How to do C++-like inheritance?

I'm stuck nearly entirely 6 hours trying a normal element inheritance in Rust, where XYZ can be accessed regardless of the node type (i.e., if let ..., match).

It's easy to do it (except the boring is and as ops from AS3/C#), buuut there's one thing Rust doesn't let me do: b = (cast b to super-type A) then cast back to sub-type B and then (unsafe { *b }).x. However, if I do unsafe { (*b).x } instead, it works; but I don't want to, because that means I'll have to use raw pointers forever.

I wanna transform that raw pointer in a normal &B or B. Why can't I? It's just 'cannot move out of dereference of raw pointer'. Why? I don't want to do the simple composition inheritance, since I wanna pass my b as a around. Can't I mimmic C++?

Short answer: You Don't. Use Traits and Delegation. Inheritance is pretty much universally recognized as an anti-pattern anyway.

9 Likes

But in my (and many other people) case traits will cause overhead, mostly because I've a base node type (Drawable) with coords. And just because I want to get the X of a node n and I don't know of which kind and struct is n, it doesn't mean I should be forced to lookup its type with match {}, if let or virtual methods.

So, a.k.a. C++/C# inheritance.

Inheritance is only needed for UI, docs, AST and drawing

I think I got it. The truth is, I don't need borrowing if I'm already using Rc<Drawable> to wrap the raw pointer.

But I lost my focus because I was doing a quick test out of my draw lib. So, yes, it makes sense Rust doesn't allow me to do that, though. I don't see reason to do it.

I think the problem you are having here is trying to apply a pattern you've used in other languages to Rust. In general Rust prefers composition over inheritance, so instead of saying a Rectangle is-a Drawable object, you might say it has-a thing which is Drawable. Rust isn't really designed with inheritance in mind, so trying to reproduce an existing OO application in Rust can feel like you're forcing a square peg into a round hole.

One resource I think you'll find super useful is Catherine West's closing keynote talk at RustConf. She had to deal with these exact same problems when working in the games industry and found that sometimes changing how you look at a problem can sometimes lead to a much more scaleable and elegant solution.

She also released a blog post which goes into more detail about the idea.

8 Likes

I'm just looking at a video what Jonathan Blow has to "rant" about, regarding RustConf and game development. Can't say anything about it yet, but I thought I mention the video ...

1 Like

When you refer to overhead here, do you mean you have to type a bit more match cases? I'm sure someone has harder numbers, but the dynamic dispatch you'd favor here is less performant.

But in any case, if what you're after is dynamic dispatch, you can do that with trait objects.

The only other part of inheritance I've found helpful is the shared struct members. If they're in the same place, one can pass an inheritor to a consumer of the parent type. The consuming function just ignores all struct members past the last one inherited. I've thought about maybe doing this with composition, i.e. a common substruct which we could call Parent, but this is doable instead using traits. The potential drawback is if you have struct members with potentially different names, types and locations, you may have to write at least one function in the trait implementation.

1 Like

Also read reddit discussion of this video, so experience will be less one-sided.

5 Likes

Thanks for the link :+1:

Hm, I see. So in Rust we better program the functionality without nodes. That also means, hm, that DOM elements from stdweb aren't fine in pure Rust.

I knew about opposite composition (i.e., where base struct contains sub structs), but it's specifically not good for trees, still.

I was never used to inheritance when I was beginner. I was just used to JS arrays, but then I got on AST, XML, these things, then I got recursive.

Rust, in general, works great with "Trees" which are DAG's (Directed Acyclic Graphs), it doesn't particularly like Cyclic-Graphs though. You can build them by using Rc/WeakRef (or equivalent in Arc), but, there is overhead and complexity with that that is best avoided. In other words, architecturally, cyclic graphs are bad (in any language), but, Rust makes you have to experience pain to create Cyclic-Graphs rather than pretending they're just the same as a DAG (which is much cleaner). If you think about it, Inheritance is a cyclic graph rather than a DAG as it appears. The base class has to reference the child classes and the child classes have to reference the base classes (though somewhat indirectly).

3 Likes

How so?

1 Like