一个结构体是否可以扩展现有的结构体,同时保留所有字段?

使用 rust 1.2.0

问题

我仍然在学习 Rust (来自 Javascript 背景)的过程中,并试图弄清楚是否有可能让一个结构 StructB扩展现有的结构 StructA,使得 StructB具有在 StructA上定义的所有字段。

在 Javascript (ES6语法)中,我基本上可以这样做..。

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;
}
}

约束

  • StructA是从一个外部 cargo包,我没有控制。

当前进展

我找到了这个 关于单继承的博客文章听起来正是我需要的。

但是试图实现它会导致这个错误消息 error: virtual structs have been removed from the language。一些搜索后,我发现这是 实现,然后根据 RFC-341删除相当快。

也发现这个 关于使用 trait 的线索,但因为 StructA是从一个外部货物包,我不认为它是可能的,我把它变成一个特点。

那么在 Rust 中实现这一点的正确方法是什么呢?

57059 次浏览

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());
}

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.

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.

One thing that is good to mention for new corners in 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 Aminal for Bird {}
impl Behavior for Bird {}
impl Attack for Bird {}

Ok, the answer is very simple. Use macro.

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

And then use it like this:

Person! {
pub stuct Kid {
extra_field: String,
}
}