Back

Rust is beyond object-oriented, part 3: Inheritance (2023)

37 points5 daysthecodedmessage.com
xlii1 day ago

After my adventures with different languages I'm on the hill that Rust traits, Haskells typeclasses, (sic!) Go's interfaces, etc. are state-of-art features in modern programming languages.

It's not like they're without the footguns - they have plenty. But even with those footguns typeclasses (as I'd rather call them) are complexity escape hatches.

E.g. at some point your type can have dozen of different implementations and you can decide to ignore that fact completely while proceeding to work on your (very narrow) piece of pie ignoring vastness of universe[^0] you found yourself in.

That being said multiple inheritance fits the same category and with minimal squinting same features can be found in languages such as Clojure or Elixir.

As other commenters mentione this is not a secret by any means, as composition over inheritance advice is decades old, but IMO still worth repeating because there might be someone who doesn't know it yet :)

[^0]: By universe I mean code implementional universe, not The Universe

santiagobasulto1 day ago

Yes I agree with you. The pattern is "composition" vs "inheritance". Defining a "thing" as "what it can do" instead of "what it is". Instead of saying that "a duck is a Bird which in turn is an Animal which in turn is a LivingThing" (Duck -> Bird -> Animal -> LivingThing) you focus on what a duck can do: a duck "quacks, swims, etc":

    class Duck(Swimmable, Quackable, FishEatable...)
I think there's still a place for "inheritance" based approach for APIs that need to be very strict about subtyping: would be hard to express covariance/invariance/contravariance without it.
gf0001 day ago

I don't see what go's interfaces have to do with the first two. It's just structural typing, isn't it?

spoiler1 day ago

I think one of those pedagogical half-truths useful for onboarding people onto an idea across different languages or getting parts of a point across, and they cover some similar use-cases.

It's a bit like saying JavaScript's prototypes are classes even though they're technically not (even with the introduction of the `class` syntactic sugar), but for casual discussions it's probably fine to just say "JS class".

But to your point: I wouldn't really phrase the way the GP did; it makes it seem like they're on the same level of usefulness as a type class!

setopt1 day ago

I really miss explicit support for type classes in languages that I use regularly like Python.

misja1111 day ago

I see the author fighting really hard against a paradigm that was already well known to be suboptimal 20 years ago .. "Favor composition over inheritance" is a phrase that became common knowledge in e.g. the Java world half way during the 2000's.

It was even already mentioned in the famous Design Patterns book from the GOF in 1994.

goatlover1 day ago

Can't help but wonder if that isn't more the fault of Java's OOP design. Favoring composition over inheritance doesn't seem like something Smalltalkers and Rubyists had to worry as much about.

ch_1231 day ago

The GoF book predates Java, and many of the examples in the book are in Smalltalk - so I think it's quite likely that composition over inheritance came from the Smalltalk community.

Problems with inheritance, such as the fragile base case problem, are intrinsic to inheritance, and not any specific language's implementation of it.

hosh1 day ago

Ruby does have language features for composition, and “composition over inheritance” has popped up in the community. I have also looked into the use of traits in Smalltalk.

lloeki1 day ago

Absolutely, usage of inheritance is (comparatively to Java/C++/Python) quite rare in Ruby.

People favour modules to compose (e.g Enumerable), and more generally interfaces (e.g duck-typed StringIO vs IO/File/etc...)

If you throw in RBS and start to type Ruby things then you start to see that Go-style interfaces pop up quite frequently.

misja1111 day ago

It was definitely also a fault of initial OOP in Java. Java's interfaces became a bit more flexible later on.

wk_end1 day ago

What's this referring to?

Mikhail_Edoshin1 day ago

I disagree that OOP tries to be intuitive. I'd say the idea of object-oriented programming is to have better composability. Sure, there are other explanations, especially in textbooks. But the underlying problem OOP tries to solve is composability and reusability of software parts.

zozbot2341 day ago

It's worth noting that you can replicate implementation inheritance pretty much as-is in Rust by using the generic typestate pattern. This is effectively what OP calls "policies" except that the policies can also bundle data of their own, just like our derived classes of old; they aren't just limited to picking some statically-known behavior at compile time.

The key difference compared to OOP inheritance is simply that generic typestate is explicitly anti-modular: your "base" and "derived" slices are expressly contained within a single program module. You still have extensibility - you can create new "derived" slices to go with some existing "base" - but now there is no attempt to use implementation inheritance as a "programming in the large" strategy or pattern; that is done by using more robust modularity mechanisms.

azangru1 day ago

Pardon my ignorance, both about the history of computing and about Rust, but weren't the "new" keyword, and the term "instance" introduced in object-oriented languages? If yes, isn't it deceptive then that Rust has the "new" method, and that the Rust book describes it as creating an instance?

aw16211071 day ago

> If yes, isn't it deceptive then that Rust has the "new" method, and that the Rust book describes it as creating an instance?

What's "deceptive" about anything here?

The name "new" has no special meaning in Rust. It's conventionally used to denote a method that creates a new instance of a struct, but there's absolutely nothing in the language itself that forces you to use that (or a similar) name.

I don't think object-oriented languages have exclusive rights to the word "instance", either? It's a pretty natural word to use in that context, and it's been used like that for a long time. For example, from K&R C second edition:

> If the declaration is tagged, however, the tag can be used later in definitions of instances of the structure.

Or

> Therefore, it is impossible to declare a structure or union containing an instance of itself.

barrenko1 day ago

If I squint a little, this is similar to https://fsharpforfunandprofit.com/.

jqpabc1235 days ago

Inheritance may have some limited utility but it often degenerates into "spaghetti code" dressed up to appear modern and acceptable.

Just like "goto" in the days of old, it is best used sparingly.

WCSTombs5 days ago

There's a now well-known guideline that says "prefer composition over inheritance." When I saw that, it clarified a lot of things. While much of software design can theoretically be approached in terms of class hierarchies, that's rarely the right place to begin, since after you start preferring composition, inheritance is mostly just a tool to achieve polymorphism.

It's a bit of a tangent, but I think the "class Dog extends Animal" type of tutorial did a lot of damage to impressionable programmers. Because it's completely abstract and basically meaningless, it's impossible to look at it critically and discuss why you would choose this or another approach, so the idea of class hierarchies just becomes a sort of dogma (so to speak).

zamadatix1 day ago

I just went through a CSCI degree program after a long time in the field (figured I'd have some fun + try to catch a few things I may not have had a chance to mess with much over the years, like databases) and I gave myself a pretty decent lean on C++ programming courses where I could.

The introduction to class inheritance started with the classic "Dog extends Animal" example. Honestly though, that was actually really good for new programmers in the course for exactly the reasons you point out: it's completely abstract and meaningless without immediately inviting urges of "well, why wouldn't I just do something like <x> instead" yet is also relatively straightforward way to learn "well, what the hell even IS class composition?" before you move on to the "more practical" examples where it can start to be better to dive into "well, when and why would I USE inheritance over something else?" type probing.

The course actually did pretty good with both halves of that around the Dog/Animal example... and then it all fell off the rails a bit as inheritance was given far too much weight in the content of that and the following courses. I think even my last C++ final project still had using inheritance as a design requirement, and by that point the things the projects were about weren't really great fits for inheritance so you had to just shove it in anyways.

To me it seems exactly like how implementing "uint32_t factorial(uint32_t x)" is a GOD AWFUL practical example of when to use recursion but a FANTASTIC way to introduce what a recursive function is, because that's exactly how you already learned about it in math classes. In the same way, once recursive functions are introduced with a basic understanding of what it is, it's good to move to more practical examples and poke at "why was that actually not a great way to implement factorial but it was great for traversing trees or..." type exploration/questioning.

HelloNurse1 day ago

Meaningless toy examples have the problem that they can appear to make sense, planting the seeds of terrible ideas.

If in a course example Dog extends Animal, it can be an arbitrary demonstration of language mechanisms (with an uncontroversial is-a relationship) but even in that case it is implicitly suggested that it is a good or "normal" design, implying an alarmingly complex program that has good reasons to deal with those two types.

Such a program is usually not described for brevity, giving the false impression that it exists: if the problem were analyzed with any diligence, usually Dog would appear a completely pointless complication.

+1
zamadatix22 hours ago
riffraff1 day ago

IIRC "Object Oriented Software Construction" by Bertrand Meyer makes it a point to argue against common inheritance examples (Rectangle < Shape, Employee < Person, etc), while still arguing for the utility of (multiple!) inheritance.

It's an interesting, if dated, read.

jqpabc1234 days ago

did a lot of damage to impressionable programmers.

As they say, "Those who can do. Those who can't teach".

tonyedgecombe1 day ago

I always disliked that phrase. In my experience one of the best ways to check whether you understand a subject fully is to teach a course on it.

llmslave21 day ago

Inheritance is great, just depends how you use it.

I've never once reached for it for building servers.

For games? All the time.

flohofwoe1 day ago

> For games? All the time.

Hmm, but game objects is exactly the popular use case where traditional inheritance breaks down first and composition makes much more sense? That's why the whole Entity-Component idea came up decades ago (like in Unity, long before the more modern column-database-like ECS designs).

llmslave21 day ago

Entity systems still rely on inheritance as you typically have an Entity baseclass that all entities derive from. That's just a single level of inheritance. Unity does this through MonoBehavior. Unreal is more inheritance-heavy but developers typically subclass something like `Actor` to one level of additional inheritance beyond the Engine's subclasses. A lot of engines will have multiple superclasses behind a Entity class, but that's an implementation detail of the engine itself and to the game developer, it's treated as a single base class that is typically subclassed a single time for the entity itself.

Even in ECS's you will often find inheritance. Some implementations have you inherit a Component struct, others will have the systems inherit a System class.

I'm sure it's still used today in some engines and by some developers but the overwhelming opinion is that doing something like Entity -> Actor -> Monster -> Orc -> IceOrc is a bad idea. Instead it would be like

   class IceOrc : Entity { components {Health, Enemy, PlayerSeek, Position, etc} }
Where each component is like

   class Health : Component { value = 100 }
And yeah, they favour composition re. Components, it's just that the components tend to inherit from a Component class. But I would still call it composition!
+1
flohofwoe1 day ago
echelon1 day ago

The only two domains where I've felt inheritance is useful are video games and windowing systems.

Inheritance is actually useful for Widget > Input > TextBox since methods and behaviors do follow parent-child and even sibling relationships.

But there aren't many domains like this.

Rust and other languages choosing traits and type classes instead of strict species-oriented class inheritance seems like the much more modern and more widely applicable approach.

Classes feel clinical and dated.

llmslave21 day ago

Anything where you have literal "instances" of something with common state/behaviour is prime for inheritance, but that's different from trying to model a domain as a set of hierarchal objects.

But I do like classes - you can use them without inheritance, and the other stuff that comes with them (encapsulation, polymorphism, etc) fits my mental model. [0] Classes are just syntactic sugar over closures at the end of the day.

But inheritance is best when it's limited and shallow.

0:

  The venerable master Qc Na was walking with his student, Anton. Hoping
  to prompt the master into a discussion, Anton said "Master, I have
  heard that objects are a very good thing - is this true?" Qc Na looked
  pityingly at his student and replied, "Foolish pupil - objects are
  merely a poor man's closures."

  Chastised, Anton took his leave from his master and returned to his
  cell, intent on studying closures. He carefully read the entire
  "Lambda: The Ultimate..." series of papers and its cousins, and
  implemented a small Scheme interpreter with a closure-based object
  system. He learned much, and looked forward to informing his master of
  his progress.

  On his next walk with Qc Na, Anton attempted to impress his master by
  saying "Master, I have diligently studied the matter, and now
  understand that objects are truly a poor man's closures." Qc Na
  responded by hitting Anton with his stick, saying "When will you
  learn? Closures are a poor man's object." At that moment, Anton became
  enlightened.
+3
ahartmetz1 day ago
bonesss1 day ago

In addition to the underlying domain (and I agree, it’s no coincidence that windowed GUI widgets are common in in OOP textbooks), there’s also overlapping spectrums of language expressiveness and object-orientedness to consider.

The principle of least surprise, bad evil and scary techniques in general might be the orthodox, efficient and intuitive for maintainers in context. Templates in GUI frameworks versus business apps, for example.

bheadmaster1 day ago

Inheritance is often used as an enabler for polymorphism in languages that don't support it in any other way.

Somehow, it leaked out and convinced everyone that it is a good thing on its own.

rhdunn1 day ago

There are two types of inheritance:

- interface/API based -- this is supported in modern languages via interfaces, traits, etc.

- implementation/code based -- modern languages tend to only support single inheritance; they tend to also support default method implementations on the interfaces/traits

bheadmaster1 day ago

> interfaces, traits, etc.

Those are actually mechanisms for achieving polymorphism.

Inheritence inherently (heh) consists of "inheriting" parent class' internals and having ability to extend them. It is basically composition, polymorphism and monkeypatching in a trench coat.

goatlover1 day ago

It's interesting that Python does support multiple inheritance.

goatlover1 day ago

Also encapsulation and code reuse. Message passing if you go by the guy who coined the term.

bheadmaster1 day ago

Encapsulation is possible without inheritance. And "code reuse" is a vague concept that had nothing to do with inheritance in particular.

Not even gonna comment on "message passing".

atoav1 day ago

So it is good that most Rusts introductions tell you that you should favor composition over inheritance in multiple places:

https://rust-for-c-programmers.com/ch20/20_3_rust_s_approach...

If Rust pushes any concept it is the use of traits: https://rust-lang.github.io/book/ch10-02-traits.html