Is it possible for one struct to extend an existing struct, keeping all the fields?
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 externalcargo
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?
There is nothing that exactly matches that. There are two concepts that come to mind.
-
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
toChild
or lend out a&Person
. -
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.
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 StructB
s to pointers to StructA
s:
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());
}
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.