"extends" key word

Hello,

I don't want to create controversy with this post, but I find that inheritance is a very important feature, and the "trait" system doesn't fully meet the need.

Let's take this simplistic example :

trait InfoAnimal {
    fn say_hello() -> String;
    fn get_number_of_legs(&self) -> u32;
}

struct Animal {
    number_of_legs: u32,
    is_domestic: bool,
}

impl Animal {
    fn get_number_of_legs(&self) -> u32 {
        self.number_of_legs
    }
}

struct Cat {
    animal: Animal,
    sound: String,
}

struct Dog {
    animal: Animal,
    sound: String,
}

impl InfoAnimal for Cat {
    fn say_hello() -> String {
        "Miawww".to_string()
    }
    fn get_number_of_legs(&self) -> u32 {
        self.animal.number_of_legs
    }
}

impl InfoAnimal for Dog {
    fn say_hello() -> String {
        "Waffff".to_string()
    }
    fn get_number_of_legs(&self) -> u32 {
        self.animal.number_of_legs
    }
}

We must add the Animal member variable to all structs (Cat,Dog), and implement the "get_number_of_legs" function for each of them.

So imagine the hassle if you have very complicated structures with several layers of inheritance

Assuming you're a masochist who likes to bang out thousands of lines of code for free (just kidding :laughing: ) , you're going to tell me it's a price to pay, but in any case the compiler will make the necessary optimizations.

But according to some tests I've carried out, the assembly code generated isn't as optimized as if we had true inheritance. (and whatever the power of the optimizer, a language must not be 100% optimizer-dependent.)


code with inheritance simulation :

 struct Animal {
    number_of_legs: u32,
    is_domestic: bool,
}

impl Animal {
    fn get_number_of_legs(&self) -> u32 {
        self.number_of_legs
    }
}

struct Cat {
    animal: Animal,
    sound: String,
}

impl Cat {
    fn get_number_of_legs(&self) -> u32 {
        self.animal.number_of_legs
    }
}

code if we had the inheritance :

struct Cat extends Animal {
    sound: String,
}

if we were to get as close as possible, it will generate background code like this :

struct Cat {
    number_of_legs: u32,
    is_domestic: bool,
    sound: String,
}

impl Cat {
    fn get_number_of_legs(&self) -> u32 {
        self.number_of_legs
    }
}

It would be really interesting and useful to have the extends keyword, which allows structs to be merged.

struct Cat **extends** Animal {
    sound: String,
}

or

#[fusion(Animal)]
struct Cat {
    sound: String,
}

I fully understand that this is easy to say theoretically, but extremely difficult to apply in practice, because you have to manage polymorphism while remaining consistent with the concept of "traits".

The "trait" concept is very powerful and practical, but it doesn't answer this kind of problem.

:thinking: For example in GoLang, they have "Struct Embedding", it's not inheritance, but it allows you to simulate it partially.

I think the solution proposed by Go is a good compromise, it just lacks a keyword that defines whether or not you want to expose the methods of the fields.

in any case, thank you for taking the time to read my post, and I'd like the interactions with this post to be reflections to find a good compromise rather than messages saying "no inheritance, troubleshoot with traits".

:pray:

In my experience, inheritance works best in trivial examples like that one you have, but breaks down quickly in larger projects. It's not an abstraction that generalizes well.

I think something like "delegation" can cover the "struct embedding" case.

12 Likes

But according to some tests I've carried out, the assembly code generated isn't as optimized as if we had true inheritance.

Maybe you could expand on this or provide the example that you tested? I don't see why there would be any performance difference between this and struct composition.

3 Likes

Inheritance has been asked for before. Here's what I had to say about it in another thread.

3 Likes

Thank you for this suggestion, "delegate" remains a crate and not a native language feature. I'll see what's possible with Deref.

I'll look for the code I made to compare the generated assembly code, and I'll post it here, along with the asm.

Thanks for the link, I'll read it tonight.

That's something we hope to fix eventually.

6 Likes

You can use generics to get much closer to traditional inheritance than your example:

 struct Animal<Ext:?Sized> {
    number_of_legs: u32,
    is_domestic: bool,
    ext:ext
}

impl<Ext:?Sized> Animal<Ext> {
    fn get_number_of_legs(&self) -> u32 {
        self.number_of_legs
    }
}

type Cat = Animal<CatExt>;
struct CatExt {
    sound: String,
}

impl Cat {
    fn say_hello(&self)->String {
        self.ext.sound.clone()
    }
}
1 Like

The most direct implementation of "public inheritance" is to expose a public field:

pub struct Animal {
    number_of_legs: u32,
}

impl Animal {
    pub fn number_of_legs(&self) -> u32 {
        self.number_of_legs
    }
}

pub struct Cat {
    pub animal: Animal,
    sound: String,
}

fn foo(cat: &Cat) {
    println!("The cat has {} legs", cat.animal.number_of_legs())
}

1 Like

The gdext project, which lets you write code for Godot in Rust, they've implemented “derive” inheritance, which is an interesting approach.

ex :

Another library that has some sort of OO interoperability would be the Rust GTK bindings, of course in that case GTK is C (not C++) with OO. I haven't looked in detail on how those bindings work or what the API is like.

Other language and library bindings might also be worth checking out (e.g. Qt, general C++ bindings, PyO3, etc)

1 Like

Exactly. IMHO most coders don't get that OOP is not universally useful in practice, just like any other paradigm. Every tool has practical limits and should be used when appropriate. Languages like Haskell, Rust, Smalltalk, Lisp, Forth and assemblers can teach one so many ways of describing their intentions, that they will soon forget that OOP even exists and consider it a niche tool. Then I wake up.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.