3 ms·
I can't speak for all Rust developers, but I try to avoid getting into situations where I need lots of properties and features on one type to begin with. Simil
by Rusky 6y ago
I can't speak for all Rust developers, but I try to avoid getting into situations where I need lots of properties and features on one type to begin with.
Similar to the usual rule of thumb about keeping functions small, keeping types small requires that the APIs you're using support you in that goal. You're totally right that, once you're trying to pass a CustomButton somewhere that wants a T: LayoutView, where LayoutView: View, it's already too late for that!
Some approaches I've used instead of the "all the traits from Button" approach:
* Maybe I don't need quite that much abstraction, and I can instead use concrete types in the layout tree (to run with the Button example). Maybe instead of replacing an entire Button with a CustomButton, I can just set some fields on a Button, or inject the Custom part into a "userdata"-like field of Button.
* Maybe I don't need everything about CustomButton to be stored in one place, and I can instead use a CustomButtonLayout type in the layout tree. Maybe the "rest" of the CustomButton just holds a reference or handle to connect it with CustomButtonLayout. (This is where people will hype the ECS pattern. IMO that is easy to go overboard with, but it is a good source of alternatives.)
* The two points above are basically just each other's inverse, while still assuming a LayoutView trait or similar. But sometimes that is the thing getting in the way, and I just need to redraw the API boundaries to avoid the problem. Maybe a lower-level API that shifts more work to the client isn't so bad after all, and then it can just work with rectangles. Or maybe a higher-level API can take on the responsibilities of CustomButton and avoid the need for so many traits.
To be fair, though, I do still wind up with inheritance-like patterns occasionally:
* I have used `struct Base<D: Derived> { .., derived: D }` where the methods of `trait Derived` take a reference to `derived` and some of the other contents of `Base`. This emulates virtual methods and protected fields, where `Base`-using code works in terms of `Base<dyn Derived>`.
* I have used `struct Derived { base: Base, .. }` with a `trait HasBase { fn base(&self) -> &Base; }`. This is closer to what you describe, but `Base`-using code instead works in terms of `B: HasBase` or `dyn HasBase`, and sprinkles in `.base()` to access `Base` directly, which collapses most of the annoying delegation boilerplate. (You can even hide the `.base()` calls if you replace `HasBase` with `Deref<Target = Base>`, though I prefer not to do that.)
* I would eventually like to see fields in traits (as in this RFC: https://github.com/rust-lang/rfcs/pull/1546 https://github.com/rust-lang/rfcs/pull/1546). This would be a lot like the previous point, but a little more direct and perhaps more efficient. And also reminiscent of row polymorphism, which appeals to the language designer in me. :)