在Rust中,“子类化”特质

23
我遇到了一个情况,我的几个结构体应该实现多个trait,但它们都至少实现了一个共同的trait。当我获取这些混合结构时,我希望将它们全部视为具有共同trait的结构:将它们作为方法参数传递,其类型为该trait,将它们存储在为该trait类型定义的集合中等等。
我一直没能想出如何做到这一点。这里是一些代码,我尝试按照建议的方式进行,但它无法编译:
trait ThingWithKeys {
    fn use_keys (&self) -> String;
}

//////

trait CorrectionsOfficer {
    fn hitch_up_pants (&self) -> String;
}

trait CorrectionsOfficerWithKeys: ThingWithKeys + CorrectionsOfficer {}

struct CorrectionsOfficerReal {}

impl ThingWithKeys for CorrectionsOfficerReal {
    fn use_keys (&self) -> String {
        String::from ("Clank, clank")
    }
}

impl CorrectionsOfficer for CorrectionsOfficerReal {
    fn hitch_up_pants (&self) -> String {
        String::from ("Grunt")
    }
}

impl <T: ThingWithKeys + CorrectionsOfficer> CorrectionsOfficerWithKeys for T {}

//////

trait Piano {
    fn close_lid (&self) -> String;
}

trait PianoWithKeys: Piano + ThingWithKeys {}

struct PianoReal {}

impl ThingWithKeys for PianoReal {
    fn use_keys (&self) -> String {
        String::from ("Tinkle, tinkle")
    }
}

impl Piano for PianoReal {
    fn close_lid (&self) -> String {
        String::from ("Bang!")
    }
}

impl <T: ThingWithKeys + Piano> PianoWithKeys for T {}

//////

trait Florida {
    fn hurricane (&self) -> String;
}

trait FloridaWithKeys: ThingWithKeys + Florida {}

struct FloridaReal {}

impl ThingWithKeys for FloridaReal {
    fn use_keys (&self) -> String {
        String::from ("Another margarita, please")
    }
}

impl Florida for FloridaReal {
    fn hurricane (&self) -> String {
        String::from ("Ho-hum...")
    }
}

impl <T: ThingWithKeys + Florida> FloridaWithKeys for T {}

//////

fn main() {
    let corrections_officer_ref: &CorrectionsOfficerWithKeys = &CorrectionsOfficerReal {};
    let piano_ref: &PianoWithKeys = &PianoReal {};
    let florida_ref: &FloridaWithKeys = &FloridaReal {};

    use_keys (corrections_officer_ref);
    use_keys (piano_ref);
    use_keys (florida_ref);
}

fn use_keys (thing_with_keys: &ThingWithKeys) {
    println! ("{}", thing_with_keys.use_keys ());
}

以下是编译错误:

Compiling playground v0.0.1 (file:///playground)
error[E0308]: mismatched types
  --> src/main.rs:80:19
   |
80 |         use_keys (corrections_officer_ref);
   |                   ^^^^^^^^^^^^^^^^^^^^^^^ expected trait `ThingWithKeys`, found trait `CorrectionsOfficerWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&CorrectionsOfficerWithKeys`

error[E0308]: mismatched types
  --> src/main.rs:81:19
   |
81 |         use_keys (piano_ref);
   |                   ^^^^^^^^^ expected trait `ThingWithKeys`, found trait `PianoWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&PianoWithKeys`

error[E0308]: mismatched types
  --> src/main.rs:82:19
   |
82 |         use_keys (florida_ref);
   |                   ^^^^^^^^^^^ expected trait `ThingWithKeys`, found trait `FloridaWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&FloridaWithKeys`

error: aborting due to 3 previous errors

基本上,它仍然找不到 XxxWithKeys 实现中的 ThingWithKeys 实现。
1个回答

46
在Rust中,特质继承与面向对象(OOP)继承不同。特质继承只是指定要求的一种方式。trait B: A并不意味着如果一个类型实现了B,它将自动实现A;而是意味着如果一个类型实现了B,它必须实现A。这也意味着如果实现了B,则必须单独实现A
例如,
trait A {}
trait B: A {}

struct S;

impl B for S {}

// Commenting this line will result in a "trait bound unsatisfied" error
impl A for S {}

fn main() {
    let _x: &B = &S;
}

然而,如果想要一个类型在实现了AB时自动实现C(从而避免手动为该类型实现C),则可以使用泛型impl

impl<T: A + B> C for T {}

在你的示例中,这意味着

impl<T: Florida + ThingWithKeys> FloridaWithKeys for T {}

请查看此论坛帖子以获取更多信息。

另外,对于PianoWithKeys而言,您不需要ThingWithKeys的约束条件,因为Piano已经需要ThingWithKeys

编辑(根据您的评论和问题编辑):

如前所述,在Rust中,特质继承与OOP继承不同。即使trait B: A,也无法将B的特质对象强制转换为A的特质对象。如果您别无选择,只能原样传递特质对象到方法中,则使用泛型可行:

fn use_keys<T: ThingWithKeys + ?Sized>(thing_with_keys: &T) {
    println! ("{}", thing_with_keys.use_keys ());
}

通用方法同样适用于类型引用(非特质对象)。

另请查看:为什么Rust不支持特质对象向上转型?


圣诞快乐!我刚在 Playground 里尝试了一下,好像没起作用。可能是我做错了。我想向你展示我做的事情,但 StackOverflow 在评论中对代码格式进行了可怕的处理。总之,问题是它仍然抱怨 XxxWithKeys 不是 ThingWithKeys,不能传递到 use_keys 方法中。(我知道:我会编辑原始问题,将我的新代码放进去。) - Dan Wiebe
哇。我刚刚弄明白了通用方法签名的问题并解决了关于Sized的问题,但我万万没有想到尝试+?Sized,因为它是一个引用,而所有引用的大小都是相同的。你需要+?Sized有什么充分的理由吗?还是只是一个必须记住的神奇咒语?(顺便说一句,谢谢 - 现在它运行得很好。) - Dan Wiebe
这是因为通用类型参数默认情况下是 Sized 的。然而,特质类型不是。为了允许类型参数接受不定长(特质)类型,您必须添加不定长 (?Sized) 约束。 - EvilTak
此外,类型参数绑定到引用的类型,而不是引用本身的类型。 - EvilTak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接