Nine Ways to Do Inheritance in Rust, a Language Without Inheritance

101 points carlk22 25 comments

The article presents each inherence problem as a puzzle to solve in Rust without inherence. Here is a list of spoilers followed by what surprised me:

  • Giving Every Integer a Shared Helper Method Trait Default Methods
  • Making an Animated Servo Still Count as a Servo Supertraits
  • Adding a Method to a Type You Don’t Own Extension Traits
  • Giving a Tiny Enum a Full Set of Standard Behaviors Derive-Generated Implementations
  • Making a Wrapper Feel Like the Thing Inside It Deref Method Lookup
  • Adding union() to Any Collection of Range Sets Blanket Implementations
  • Treating Fifteen Integer-Like Types the Same Way Macro-Generated Implementations
  • Giving Only OutputArray<8> a Byte-Oriented Method Constraint-Gated Methods
  • Saving Only Serializable Values to Flash Method-Level Constraints

What surprised me:

You can almost (but not quite) inherit from a concrete type via the Deref trait. The example wraps a String in a HtmlBuffer struct. If you implement Deref, you can use most String methods directly on HtmlBuffer. (Aside: You can but probably shouldn't do this.)

I use declarative macros to implement a trait named Integer on the 12 numeric integers (u8...i128), plus ip4, ip6, and char. After writing the article, I started wondering if it could/should be done with just traits instead. Now I think macros is best because: a) using 3rd party num-traits crates doesn't work because the compiler worries that num-traits might add char later b) even defining your own "subtraits" for the numeric integers and the IP integers doesn't work because the compiler worries about future overlapping types in the subtraits. c) I think there is a marker-trait solution, but it is more complicated than just using macros.

I realized that I think too much in terms of hierarchy and inheritance. Rust shows that this is not necessary or desirable.