Why not inheritance?


#1

Probably this proposal was considered and rejected long ago. I have base class (pseudo-code):

Widget{
  position
  size
  background color
  base methods
}

TreeView {
  w: Widget;
  things specified to TreeView;
}

Window {
  w: Widget;
  things specified to Window;
}

I must call win.w.size. It is would be possible syntax sugar to calling win.size instead indirect by field w?


3 weeks to delegation! Please help
#2

You might want to check out this topic, which answers kind of the same question: Why not just add classes?

Now to answer your question of whether there exists a syntax sugar for forwarding to functions on your base type: The Deref pattern might be what you’re looking for.


#3

What we’re considering along these lines is something like this (syntax may vary):

trait Widget {
    struct {
        position: Position,
        size: Size,
        bgcolor: Color,
    }
    // methods
}

That is, allowing traits to define fields as well methods. We’re also considering sugars for ‘delegation’ as you’ve described, so that your types could all delegate their Widget trait impl to their Widget struct they have. Together these would give you inheritance.


#4

Inheritance is sometimes handy. But given the army of programmers that know OOP and inheritance, what’s stopping them to use that feature as first solution to lot of coding design problems?


#5

By breaking inheritance into its components, you are unlikely to reach for all of it immediately, but just the parts you need:

  • Traits give you polymorphic abstractions.
  • Delegation gives you sugar for types which use the same trait impl.
  • Trait objects give you dynamic dispatch.

You’re not going to immediately reach for all of these things, because they’re all individual choices and not an extremely accessible “this inherits from that” mechanism. But they allow you to express inheritance as a pattern, emergent from our other language features, for situations where you need it.


#6

I was recently playing around with sub-types and inheritance and created this gist that shows off one way to produce most of the effects in a static setting. With near-minimal code you can handle functions that can’t/can/must be overloaded, along with additional data and unique functions.

I assume trait objects could make it all dynamic, but I generally work with static code and don’t know the issues involved.


#7

Thanks for that, that’s interesting! Probably not the most natural way to design things in Rust, but I’m currently struggling to port a piece of C++ code to Rust that makes heavy use of inheritance to reuse code+data across sub-classes, and that might help me…


#8

Language design should not be based on throwing up road blocks. Right now it’s not possible to design a good GUI framework in Rust.


#9

What about azul?


#10

I think this is an extraordinary claim, so shouldn’t be made without extraordinary evidence.

It’s clear that right now one cannot made a convenient GUI framework in Rust that works exactly like the ones made for OOP languages. But as we’ve seen in other places, it’s possible that a different structure will work great in Rust – Raph had some thoughts in that direction, for example: https://raphlinus.github.io/personal/2018/05/08/ecs-ui.html


#11

Reading Raph’s post, it occurs to me that the fact that it’s harder to make GUIs in Rust could be a signal that GUIs in general are harder to prove correct than other programs. Or at least, with the approaches that are common today.


#12

Reading Raph’s post, it occurs to me that the fact that it’s harder to make GUIs in Rust could be a signal that GUIs in general are harder to prove correct than other programs. Or at least, with the approaches that are common today.

No, it only signals that GUIs are hard to write given Rust’s constraints. Ralph’s post reiterates something already well known – Rust sharply limits the kinds of designs that can be implemented in it … and that’s not a good feature of a systems programming language. Again, language design should not be based on throwing up road blocks.

While Rust prevents certain classes of error, in no way are Rust programs provably correct. Your program may not have dangling pointers or cross-thread race conditions, but it can certainly panic, infinitely loop, deadlock, or give wrong results. And there are other memory-safe languages, notably those with garbage collection … Rust found a clever way to achieve memory safety without garbage collection (and relatedly, without putting nearly everything on the heap, as “managed” VM systems do), but it pays a heavy price for it. There are projects where the trade off Rust offers is highly desirable, and what Rust offers is unique and valuable, but such projects are relatively scarce and GUIs aren’t among them.

That said, future versions of Rust may open up the design space some … the mention here of ways to delegate method calls to a struct member, for instance (however, looking at Looking at https://github.com/rust-lang/rfcs/issues/349, I suspect that it will be a very long time before it comes to Rust). This is important in a language that not only has a preference for composition over inheritance, but demands it. D has such forwarding as well as other features that reduce the friction in such designs, as well as numerous other designs. (Its one big limitation is the lack of a gc-free core library – you can code in D without gc, but you lose strings and other important features. It’s too bad D doesn’t have any corporate backing. Nim is another systems programming language that has a lot of technical merit but is developed on a shoe string.)


#13

That’s quite an exaggeration. Rust doesn’t let you shoot yourself in the foot with memory safety and thread safety by default. That is certainly a kind of guarantee it makes, so your programs are provably correct at least with respect to this aspect. And memory and thread handling errors are among the most common problems one encounters in (not just systems) programming. Which means that the guarantees Rust provides are very useful and important in practice.

If by “sharp limitation”, you mean “no inheritance” – well, I beg to differ. Yes, currently most GUI libraries rely on inheritance. Does this mean that it’s necessarily the way we can or should design them? I don’t think so. I enjoyed writing code in many inheritance-enabled languages before Rust, and I enjoy writing inheritance-free code in Rust even more. I am solving problems in Rust in a somewhat different way, reaching for various useful and valuable features of the language instead of the know-it-all pattern of inheritance. (Incidentally, this also made my own code more reliable, by the way. No inheritance means no unwanted action-at-a-distance up the chain of types, which is another class of errors altogether, very specific to languages with inheritance.)

Since most mainstream languages in which GUI libraries have been traditionally written offer inheritance or a similar mechanism, there wasn’t a real demand for a design without inheritance, and the general GUI library writer and user largely didn’t even bother. It would be tunnel vision to deduce from this historical fact that the OO/inheritance way is the only way for GUIs, and that it would therefore be unnecessary, impossible, or useless to try and come up with alternative formulations of GUIs that don’t require inheritance, and play well with the idioms of Rust.


#14

C also does not have inheritance, yet many GUI systems are written in C. You can do the exact same stuff in Rust, if you wanted.


#15

This post was flagged by the community and is temporarily hidden.


#16

The more relevant point is probably that Rust supports unsafe code that can do pretty much anything C can. All of the restrictions that exist in Rust exist only to support a notion of “safe” code that is far stronger than anything you get in C or C++.

The interesting question is not can a GUI be written in Rust, because if it can be written in C or C++ it’s obviously possible in Rust. The interesting question is what new language features, if any, would Rust need in order to make a high quality GUI toolkit that exposes only safe abstractions.

I think “GUI needs inheritance” is an oversimplification of that question, because in most languages that have “inheritance”, inheritance is actually a grabbag for a dozen or so separate features. Rust already provides most of those individual features (privacy via the module system, dynamic dispatch via trait objects, arguably method call syntax falls under this, etc).

As far as I know, the important parts of the “inheritance” feature bundle that are still missing in Rust are:

  • some form of “fields in traits
  • some layout guarantees, such as ensuring the “base” type appears at the beginning of all “derived” types so that the base type’s fields can be accessed efficiently (even if client code creates new derived types that your library didn’t know about when it was compiled)

@jibal do you know of anything else Rust is fundamentally missing here?


#17

I appreciate the sensible response and the query, but I really am done here, at least for now.


#18

Rust needs a way to inspect supertraits or something, preferably at compile-time.

See eventbus for use-cases.

It’s basically inheritance.


#19

I am fundamentally in the “inheritance is the wrong solution to most problems” camps, but i have to admit that in this case the empirical evidence is on the other side. Stuff like ECS architectures for UIs are very much in the experimentation state at this point.

Imo, we should be a little more humble… rust is deliberately ill suited for classical OOP, and for GUIs we don’t have a proven architectural alternative yet. My bet is that inheritance based widget systems are a local maxima and not the best solution, but right now the criticism is valid.


#20

If someone put an honest effort into building a GUI framework in idiomatic Rust and they came out of the experiences with a critical post-mortem, it would be far easier to give weight to the criticism.

for GUIs we don’t have a proven architectural alternative yet

I think we have several, actually. Web-based GUIs which run through another language are always an option, and they suit most people’s purposes. I’ve written (most of) a special purpose GUI for a game, and I’ve had no trouble with Rust either. You can’t just say “Rust doesn’t do GUIs well”, when there are thousands of ways to do GUIs. You just can’t blindly port OOP-based GUIs directly into Rust.