I have some C++ code that implements a UI library. There is a root View class with subclasses like Button, VStack, TextField, etc. The View base stores certain things like it's "frame" (rectangle for location in parent view or window). So some of it's functions are non-virtual. Then there are certain virtual functions like layout and render that all the classes implement. Classes like VStack contain std::vector<View*> children.
I read that Rust has no inheritance, and that composition is another way to do this. But in this case it seems like inheritance is more straightforward. So I'm not sure how I would port parts of this to Rust. I guess there will be a View trait that the various structs can implement. That replaces the virtual functions. But what about the common implementation parts, like all View's having a frame: Rectangle. Would you just repeat it in each struct? Even if it was at the same offset in each struct, would I then need a trait function like get_frame to access it if I had something like std::vector<View*> (whatever the equivalent in Rust is).
Rust truly has no implementation inheritance so you really do need to switch to composition. It is not unusual that an existing design cannot be ported to Rust. This earlier thread may help:
Yes, Rust has no inheritance, but has Box<dyn Trait>.
If you just convert SubClass to ParentClass in Rust, then Box<dyn Trait> is a good choice.
But if you want cast ParentClass to SubClass, downcast_ref in std::any module may help.
When I prepare write AST for parser,
first time I use Box<dyn ASTNode> for it, just like what I do in C++, but quickly I find out code for ParentClass to SubClass is too hard. Then I use a enum ASTNode to handle all the cases...
If you use a View trait which inherently needs access to the Rectangle, you would have something like a getter method as part of the trait, yes. If every trait View implementer is its own independent type, yes, this also probably this means that they all have a field: Rectangle field.[1] It need not have the same name or be at the same "offset".[2] (It need not even exist; maybe some widget is always zero-sized at offset 0,0 or something.)
Alternatively to every implementer being an independent type, however, you could use generics with composition.
// No extra bounds on `T`, though maybe you would want/need
// `where ViewWidget<T>: View`
impl<T> ViewWidget<T> {
fn non_virtual_method_from_cpp(&self) { /* ... */ }
}
and maybe you don't need the Rectangle getter on the trait anymore.[3]
These aren't the only possibilities; just a short example of what you might consider.
Offsets don't matter unless you're using an explicitly specified layout (like #[repr(C)]) and unsafe -- don't do that here (it's something you might do for FFI or the like). ↩︎
If you use dyn View, you probably still need it. If you use enums instead, you might not. Though if other generic consumers of the trait need to get the Rectangle, you still might need it for that reason. ↩︎