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

dre*_*els 28 inheritance struct rust

运用 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

约束

  • StructA来自cargo我无法控制的外部包装.

目前的进展

我发现这篇关于单继承的博客文章听起来就像我需要的那样.

但是尝试实现它会导致出现此错误消息error: virtual structs have been removed from the language.有些人稍后搜索,我发现它已经实现,然后很快就按照RFC-341删除了.

还找到了关于使用特征的这个帖子,但由于StructA来自外部货物包装,我认为我不可能把它变成特性.

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

She*_*ter 28

没有什么能与之完全匹配.有两个概念浮现在脑海中.

  1. 结构组成

    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());
    }
    
    Run Code Online (Sandbox Code Playgroud)

    您可以简单地将一个结构嵌入另一个结构中.内存布局很好而且紧凑,但你必须手动委派所有方法PersonChild或者出借一个&Person.

  2. 性状

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

当然,您可以将这两个概念结合起来.


作为DK.提到,你可以选择实施DerefDerefMut.但是,我不同意这些特征应该以这种方式使用.我的论点类似于将经典的面向对象继承简单地用于代码重用的论点是错误的."赞成合成而非继承"=>"赞成合成Deref".但是,我确实希望能够实现简洁授权的语言功能,减少构图的烦恼.

  • @Shepmaster:为'Child`实现`Deref <Target = Person>`和`DerefMut`将大大有助于使合成方法很好地工作.因为那时你可以在大多数情况下将"孩子"视为"人". (2认同)
  • 有这方面的消息吗? (2认同)

DK.*_*DK. 21

Rust没有任何类型的结构继承.如果要StructB包含与之相同的字段StructA,则需要使用合成.

struct StructB {
    a: StructA,
    // other fields...
}
Run Code Online (Sandbox Code Playgroud)

另外,为了澄清,特征只能定义方法和相关类型; 他们无法定义字段.

如果你想能够使用a StructB作为a StructA,你可以通过实现和traits 来获得一些方法,这将允许编译器隐式地将指针转换为指向s的指针:DerefDerefMutStructBStructA

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());
}
Run Code Online (Sandbox Code Playgroud)


小智 7

对于 Rust 新手来说,值得一提的一件事是设计结构/类/特征的方法。尽量保持你的特质小而简单。

并利用可能对同一类使用多个特征:

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 {}
Run Code Online (Sandbox Code Playgroud)


use*_*730 6

另一种选择是使用泛型:

trait IAnimalData {}

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

struct Dog {
    favorite_toy: String,
}

impl IAnimalData for Dog {}
Run Code Online (Sandbox Code Playgroud)

然后你可以实现这样的“子”方法,它只适用于狗:

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

如果你想要适用于所有动物的父方法,你可以像这样实现它们:

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

好的部分是您不必经历将方法转发Dog到方法中的痛苦Animal;你可以直接在里面使用它们impl Animal<Dog>。此外,您可以AnimalAnimal<Dog>. 不好的部分是您的继承链始终可见(也就是说,您可能永远不会Dog在代码中使用,而是使用Animal<Dog>)。此外,如果继承链很长,您可能会得到一些非常愚蠢、冗长的类型,例如Animal<Dog<Chihuahua>>. 我想那时最好使用类型别名。

  • @trentcl“笑你”有点刺耳。惯用语,不,但是任何取笑别人惯例的人都需要自我评估。 (7认同)
  • 这是一个很好的解决方案,但其他 Rust 程序员可能会嘲笑你命名特征“ITraitName”。只要“TraitName”就可以了。特征和类型不属于同一命名空间,因此很少有任何混淆(至少,只要您对特征对象类型使用“dyn”)。 (5认同)