122

Using rust 1.2.0

Problem

I'm still in the process of learning Rust (coming from a Javascript background) and am trying to figure out if it is possible for one struct StructB to extend an existing struct StructA such that StructB has all the fields defined on StructA.

In Javascript (ES6 syntax) I could essentially do something like this...

class Person {
    constructor (gender, age) {
        this.gender = gender;
        this.age = age;
    }
}
class Child extends Person {
    constructor (name, gender, age) {
        super(gender, age);
        this.name = name;
    }
}

Constraints

  • StructA is from an external cargo package that I have no control over.

Current Progress

I found this blog post on single-inheritance which sounds like exactly what I need.

But trying to implement it resulted in this error message error: virtual structs have been removed from the language. Some searching later and I found out that it had been implemented and then removed per RFC-341 rather quickly.

Also found this thread about using traits, but since StructA is from an external cargo package I don't think it is possible for me to turn it into a trait.

So what would be the correct way to accomplish this in Rust?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
drebabels
  • 1,992
  • 3
  • 17
  • 13
  • I think it will always be sub-optimal to answer this question without knowing the context in which you want to use the newly generated struct and why. In my opinion these design decisions highly depend on the specific use-case. – Jan Sep 30 '20 at 10:22

5 Answers5

100

There is nothing that exactly matches that. There are two concepts that come to mind.

  1. Structural composition

    struct Person {
        age: u8,
    }
    
    struct Child {
        person: Person,
        has_toy: bool,
    }
    
    impl Person {
        fn new(age: u8) -> Self {
            Person { age: age }
        }
    
        fn age(&self) -> u8 {
            self.age
        }
    }
    
    impl Child {
        fn new(age: u8, has_toy: bool) -> Self {
            Child { person: Person::new(age), has_toy: has_toy }
        }
    
        fn age(&self) -> u8 {
            self.person.age()
        }
    }
    
    fn main() {
        let p = Person::new(42);
        let c = Child::new(7, true);
    
        println!("I am {}", p.age());
        println!("My child is {}", c.age());
    }
    

    You can simply embed one struct into another. The memory layout is nice and compact, but you have to manually delegate all the methods from Person to Child or lend out a &Person.

  2. Traits

    trait SayHi {
        fn say_hi(&self);
    }
    
    struct Person {
        age: u8,
    }
    
    struct Child {
        age: u8,
        has_toy: bool,
    }
    
    impl SayHi for Person {
        fn say_hi(&self) {
            println!("Greetings. I am {}", self.age)
        }
    }
    
    impl SayHi for Child {
        fn say_hi(&self) {
            if self.has_toy {
                println!("I'm only {}, but I have a toy!", self.age)
            } else {
                println!("I'm only {}, and I don't even have a toy!", self.age)
            }
        }
    }
    
    fn greet<T>(thing: T)
        where T: SayHi
    {
        thing.say_hi()
    }
    
    fn main() {
        let p = Person { age: 42 };
        let c = Child { age: 7, has_toy: true };
    
        greet(p);
        greet(c);
    }
    

You can combine these two concepts, of course.


As DK. mentions, you could choose to implement Deref or DerefMut. However, I do not agree that these traits should be used in this manner. My argument is akin to the argument that using classical object-oriented inheritance simply for code reuse is the wrong thing. "Favor composition over inheritance" => "favor composition over Deref". However, I do hold out hope for a language feature that enables succinct delegation, reducing the annoyance of composition.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    This is what I suspected. Thanks for confirming and thanks for the quick answer! For the rest of the noobs out there... do these snippets compile for you? If I don't have the return type for `new` I get a compile error. – drebabels Sep 13 '15 at 20:15
  • @drebabels no, that's my mistake when transferring it across. I made sure it compiles now :-) – Shepmaster Sep 13 '15 at 20:23
  • 2
    @Shepmaster: implementing `Deref` and `DerefMut` for `Child` would go a long way to making composition approach work nicely. Because then you can in most cases treat a `Child` as though it were a `Person`. – Chris Morgan Sep 14 '15 at 00:18
  • @ChrisMorgan that may be true, but I'm in the camp of people who believe that's not semantically correct ^_^. One of these days I'll write up my RFC for a proposed alternative... someday... – Shepmaster Sep 14 '15 at 00:21
  • 2
    Are there any news on this? – xoxox Mar 21 '17 at 09:41
  • @xoxox news on **what**? The code in this answer still compiles, the text appears to be valid. We aren't mindreaders here on Stack Overflow, so you need to be more specific. – Shepmaster Mar 21 '17 at 13:10
  • 1
    @Shepmaster It would be nice to have composition of structs the way golang does it: [playground example](https://play.golang.org/p/sskWaTpJgr). It avoids implementing delegates yourselves. Have you pushed an RFC yet ? – Bruno Grieder May 07 '20 at 14:19
  • @BrunoGrieder the closest I know of is https://github.com/rust-lang/rfcs/pull/2393 – Shepmaster May 07 '20 at 14:35
63

Rust does not have struct inheritance of any kind. If you want StructB to contain the same fields as StructA, then you need to use composition.

struct StructB {
    a: StructA,
    // other fields...
}

Also, to clarify, traits are only able to define methods and associated types; they cannot define fields.

If you want to be able to use a StructB as a StructA, you can get some of the way there by implementing the Deref and DerefMut traits, which will allow the compiler to implicitly cast pointers to StructBs to pointers to StructAs:

struct StructA;

impl StructA {
    fn name(&self) -> &'static str {
        "Anna"
    }
}

struct StructB {
    a: StructA, 
    // other fields...
}

impl std::ops::Deref for StructB {
    type Target = StructA;
    fn deref(&self) -> &Self::Target {
        &self.a
    }
}

fn main() {
    let b = StructB { a: StructA };
    println!("{}", b.name());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
DK.
  • 55,277
  • 5
  • 189
  • 162
29

Another alternative is to use generics:

trait IAnimalData {}

struct Animal<D: IAnimalData> {
    name: String,
    age: i64,
    child_data: D,
}

struct Dog {
    favorite_toy: String,
}

impl IAnimalData for Dog {}

And then you can implement "child" methods like this, which will only apply to dogs:

impl Animal<Dog> {
    pub fn bark(&self) -> String {
        return "bark!".to_owned();
    }
}

And if you want parent methods that apply to all animals, you can implement them like this:

// implements the 'breathe' method for all animals
impl<T: IAnimalData> Animal<T> {
    fn breathe() {}
}

The good part is that you don't have to go through the pain of forwarding methods in Dog to methods in Animal; you can use them directly inside impl Animal<Dog>. Also, you can access any fields defined in Animal from any method of Animal<Dog>. The bad part is that your inheritance chain is always visible (that is, you will probably never use Dog in your code, but rather Animal<Dog>). Also, if the inheritance chain is long, you might get some very silly, long-winded types, like Animal<Dog<Chihuahua>>. I guess at that point a type alias would be advisable.

trent
  • 25,033
  • 7
  • 51
  • 90
user986730
  • 1,226
  • 1
  • 13
  • 12
  • 4
    This is a good solution, but the other Rust programmers may laugh at you for naming traits `ITraitName`. Just `TraitName` is fine. Traits and types aren't part of the same namespace, so there's rarely any confusion (at least, as long as you use `dyn` for trait object types). – trent Mar 20 '21 at 15:39
  • 33
    @trentcl "laugh at you" is a bit harsh. Idiomatic, no, but anyone that's poking fun over someone else's convention needs to self-evaluate. – Lee Benson May 07 '21 at 12:32
  • Good example but the code contains typos and is quite incomplete IMO: `breathe` must have the `&self` argument; `Animal` and `Dog` a `new` fn; and the code does not give `Dog` the `breathe` fn, instead you have to create and handle an `Animal` object for that. Example: `let dog: Animal = Animal::new(); dog.breathe(); dog.bark();` – ARno Jul 27 '23 at 08:29
5

One thing that is good to mention for newcomers to Rust is the way to design your structs/classes/traits. Try to keep your traits small and simple.

And take advantage of possible to use multiples traits for the same class:

trait Animal {
    fn print_name(&self);
}

trait Attack {
    fn can_kick(&self) -> bool {
        false
    }
}

trait Behavior {
    fn can_fly(&self) -> bool {
        true
    }
}

impl Animal for Bird {}
impl Behavior for Bird {}
impl Attack for Bird {}
Mr. S
  • 1,469
  • 2
  • 15
  • 27
Steven Koch
  • 629
  • 7
  • 9
0

Rust does not have struct inheritance, but this limitation can be worked around in some cases by using a macro to generate a struct.

For example, if you want to create Giraffe and Elephant structs with common elements,

macro_rules! AnimalBase {
    (#[derive($($derive:meta),*)] $pub:vis struct $name:ident { $($fpub:vis $field:ident : $type:ty,)* }) => {
        #[derive($($derive),*)]
        $pub struct $name {
            // required for all animals
            age: i64,
            $($fpub $field : $type,)*
        }
        impl $name {
            $pub fn new(age:i64,$($field:$type,)*) -> Self{
                Self{
                    age,
                    $($field,)*
                }
            }
        }
    }
}

And then use it like this:

AnimalBase! {
  #[derive(Debug)]
  pub stuct Giraffe {
     extra_field_a: String,
  }
}

AnimalBase! {
  #[derive(Debug)]
  pub struct Elephant {
    extra_field_b: String,
  }
}

// You can create a struct without additional elements, too!
AnimalBase! {
  #[derive(Debug)]
  pub struct Animal {
  }
}

The limitation of the given macro example is, the macro expects #[derive(...)] on the target struct. You may adapt this and modify it to fit your requirements.

Victor
  • 13,914
  • 19
  • 78
  • 147