多态结构体的向量,具有特定trait中的关联类型

3

我想了解如何使用带有关联类型的trait实现多态。考虑下面的trait:

trait Animal {
    fn talk (&self);
}

该特质被以下结构使用:

struct Dog;
struct Cow;

impl Animal for Dog {
    fn talk (&self) {
        println!("Woof");
    }
}

impl Animal for Cow {
    fn talk (&self) {
        println!("Moo");
    }
}

接下来我循环遍历一个 Vec<&Animal>,这里多态性能够很好地发挥作用:

fn main() {
    let animals: Vec<&Animal> = vec![&Dog, &Cow];
    for animal in &animals {
        animal.talk();
    }
}
// output:
// Woof
// Moo

目前为止,一切都很好。现在,我向特质中添加了一个相关联的类型Food(该类型未被使用,但仅用于最小限度的复制)。

struct Meat;
struct Herb;

trait Animal {
    type Food;
    ...
}
impl Animal for Dog {
    type Food = Meat;
    ...
}
impl Animal for Cow {
    type Food = Herb;
    ...
}

现在我遇到了错误:

error[E0191]: the value of the associated type `Food` (from trait `Animal`) must be specified
   --> src/main.rs:188:23
163 |     type Food;
    |     ---------- `Food` defined here
...
188 |     let animals: Vec<&Animal> = vec![&Dog, &Cow];
    |                       ^^^^^^ help: specify the associated type: `Animal<Food = Type>`

但在此情况下,我不能仅仅遵循错误消息,因为实现特质 Animalstruct数量不应该是静态的。
那么解决这个问题的 Rust 方法是什么呢?提前致谢!

1
你可能需要在你的trait中定义一个 enum Food { Meat, Herb } 和一个 fn food(&self) -> Food - eggyal
我仍在学习Rust,但我不认为我可以在这里将 MeatHerb 转换为枚举。 这是一个最小的可复制示例,在实际情况中这些是非常复杂的结构体。 我的问题不是关于改编代码,而是关于在Rust中使用具有关联类型的多态向量的总体情况。 但也许我误解了你的回答。 - Loheek
我认为“食品”功能不在这个问题的范围内,问题是:多态性运作良好,我有一个“main”函数,在其中可以调用子类型的“talk()”函数,而无需复制/粘贴1435个动物类型名称。但是,如果特征具有关联类型(这不是我可以更改的内容),它将不再起作用。我希望保持这个“anything.talk()”,它调用了第一次工作的正确子类型函数。我会感到惊讶,如果没有办法做到这一点。 - Loheek
不,Food 确实是问题所在。而且,你一开始就没有多态性,因为 Animal 或者说 dyn Animal,正如编译器所告诉你的那样,是一个明确、具体的类型。当你给它添加了一个关联类型时,它变成了一个无限集合的 dyn Animal<Food = T> 类型。 - Ivan C
没有显式 dyn 的特质对象仍然可以在我的版本中使用。但是感谢您提醒我,这将被弃用。然而问题仍然存在,我无法删除关联类型,并且我想要一个唯一的 anything.talk()。因此,如果我理解正确,您是在告诉我在 Rust 中基本上不可能实现这一点。 - Loheek
显示剩余5条评论
1个回答

7

&Animal 是代表 &dyn Animaldyn Animal 是一种特质对象类型,只有在给定的特质是对象安全时才存在。带有关联类型的特质不是对象安全的,因为dyn Animal无法实现Animal而不指定关联类型Food。¹

这是运行时多态性(特质对象)的固有局限性:您不知道具体的类型,因此无法确定其关联类型。²

如果要创建一个可以调用.talk()的向量,很容易就可以创建一个专门为此而创建的特质(playground):

trait Talk {
    fn talk(&self);
}

impl<A: Animal> Talk for A {
    fn talk(&self) {
        Animal::talk(self);
    }
}

let animals: Vec<&dyn Talk> = vec![&Dog, &Cow];

您无法通过 &dyn Talk 写任何使用 Food 的代码,这正是问题所在: Food 依赖于具体类型,而您的向量包含多个具体类型。

另请参阅


¹ 您可以创建一个特质对象,其类型都具有相同的关联类型,例如 dyn Animal<Food = Herb>。这在 Iterator 中很常见,例如 Box<dyn Iterator<Item = i32>>。但是当 Animal 具有不同种类的 Food 时,它并不能解决问题。

² 通过编译时多态性(泛型),您可以编写适用于任何实现 Animal 的任何 Food 类型的代码。但是您无法将不同的编译时类型放入 Vec 中,因此也无法解决该问题。


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