In this next1 post of my series explaining how Rust is better off without Object-Oriented Programming, I discuss the last and (in my opinion) the weirdest of OOP’s 3 traditional pillars.
It’s not encapsulation, a great idea which exists in some form in every modern programming language, just OOP does it oddly. It’s not polymorphism, also a great idea that OOP puts too many restrictions on, and that Rust borrows a better design for from Haskell (with syntax from C++).
As much as I understand why Rustaceans don't like classical inheritance, that's only a minor factor in Rust not having support for it.
Before 1.0, there was a project to solve Servo's needs for inheritance. An important point about this is how it made the question of whether inheritance is good or not moot — Servo implements the DOM, and the DOM needs inheritance, end of story.
Servo uses a bunch of procedural macros to implement OOP, and so do other projects in similar positions (GNOME, PyO3, Windows...). Anything worth adding to Rust needs to be better than that.
Solving inheritance for Servo means it needs to be as fast as C++ (the Internals thread linked above is mostly about this requirement). It's hard, because C++ doesn't prevent things like object slicing, and a safe implementation of subclassing for Rust would need to (while still providing control over an object's allocation like C++ does).
About object slicing: TFA describes inheritance as having a "hidden member variable" within a child class that's an instance of the parent class. This is mostly how it works, but it's important in trad OOP that the child class encapsulates the parent completely. For example, if you're implementing the DOM and an
HTMLTemplateElement
wants to implement its own clone and adopt steps, there should not be any way to bypass them. Also, it should not be possible to get a reference&mut Node
and usemem::replace
with a non-template, since that would still be a way to violate the invariants those methods seek to enforce.JS, Python, and WinRT are all substantially different. Covering all of them means the language features look less like a complete object system and more like a framework for building an object system.
Note: it's been a long time since I last made any meaningful contributions to Servo, and I never worked on the JS bridge code much. I'm reflecting the story as best I can. Please read the original Internals thread to get a deeper understanding of this, without creating a bad game of telephone.