为什么在trait对象引用的集合中必须指定关联类型?

15

以下是一个有问题的示例 (Playground):

// Some traits
trait Behaviour {
    type Sub: SubBehaviour;
}
trait SubBehaviour {}

// Some implementations of these traits
struct A;
impl Behaviour for A {
    type Sub = B;
}
struct B;
impl SubBehaviour for B {}

// Struct that holds a collection of these traits.
struct Example<'a> {
    behaviours: Vec<&'a dyn Behaviour>,
}

impl<'a> Example<'a> {
    fn add_behaviour<T: Behaviour>(&mut self, b: &'a T) {
        self.behaviours.push(b);
    }
}

fn main() {
    let b = A;
    let mut e = Example {
        behaviours: Vec::new(),
    };
    e.add_behaviour(&b);
}

I get:

error[E0191]: the value of the associated type `Sub` (from trait `Behaviour`) must be specified
  --> src/main.rs:17:29
   |
3  |     type Sub: SubBehaviour;
   |     ----------------------- `Sub` defined here
...
17 |     behaviours: Vec<&'a dyn Behaviour>,
   |                             ^^^^^^^^^ help: specify the associated type: `Behaviour<Sub = Type>`

为什么在这种情况下我们只是存储对象的引用,却必须指定类型?我该如何使这段代码正常工作?


1
我相信你的问题已经在如何拥有一个由不同关联类型的对象组成的向量?的答案中得到了解答。如果你有不同意见,请编辑你的问题以解释差异。否则,我们可以将此问题标记为已回答。 - Shepmaster
3个回答

9

所有类型必须在编译时静态知道。如果Rust允许Vec元素有不同的关联类型,类型信息可能取决于仅在运行时才知道的索引。

我发现考虑一个更小的例子很有帮助:

trait Behaviour {
    type T;

    fn make_t(&self) -> T;
}

fn foo(my_vec: Vec<&dyn Behaviour>, index: usize) {
    let t = my_vec[index].make_t(); //Type of t depends on index
}

你已经在正确的方向上解决了这个问题。 我猜你引入了 SubBehaviour trait 是因为你意识到需要对 T 进行限制。 但问题是,在这种情况下,你不再需要关联类型了。
trait SubBehaviour {}

trait Behaviour {
    fn make_t(&self) -> Box<dyn SubBehaviour>;

    fn ref_t(&self) -> &dyn SubBehaviour; // also fine
}

fn some_function(my_vec: Vec<&dyn Behaviour>, index: usize) {
    let t1 = my_vec[index].make_t();
}

唯一的限制是,在您定义Behaviour时,不能执行任何依赖于T大小的操作(例如在堆栈上分配或移动),因为SubBehaviour特征无法指定T的大小。


4

您需要指定特质的关联类型(即Behavior<Sub = ???>)。

在所有位置添加关联类型后,它可以编译:

struct Example<'a, S: SubBehaviour + 'a> {
    behaviours: Vec<&'a Behaviour<Sub = S>>,
}

impl<'a, S: SubBehaviour> Example<'a, S> {
    fn add_behaviour<T: Behaviour<Sub = S>>(&mut self, b: &'a T) {
        self.behaviours.push(b);
    }
}

在Playground上查看示例


6
这并没有真正回答提问者关于“为什么”的问题。他们已经知道需要指定类型了... - Shepmaster

4

所以你的第一个问题的答案已经被 Tim 的回答 涵盖了,并且是正确的。也许您不希望您的Example是通用的。在这种情况下,您需要使用某种类型抹除:

// Some traits
trait Behaviour {
    type Sub: SubBehaviour;
}
trait SubBehaviour {}

// Some implementations of these traits
struct A;
impl Behaviour for A {
    type Sub = B;
}

struct B;
impl SubBehaviour for B {}

struct AnyBehaviour {
    closure: Box<Fn()>,
}
impl AnyBehaviour {
    fn new<U: SubBehaviour, T: Behaviour<Sub = U>>(b: &T) -> Self {
        let closure = || {
            //let sub = T::Sub::new();
            println!("Can use T here");
        };

        AnyBehaviour {
            closure: Box::new(closure),
        }
    }
}

// Struct that holds a collection of these traits.
struct Example {
    behaviours: Vec<AnyBehaviour>,
}

impl Example {
    fn add_behaviour<U: SubBehaviour, T: Behaviour<Sub = U>>(&mut self, b: &T) {
        self.behaviours.push(AnyBehaviour::new(b));
    }
}

fn main() {
    let b = A;
    let mut e = Example {
        behaviours: Vec::new(),
    };
    e.add_behaviour(&b);
}

在这个封闭环境中,您可以访问所有需要调用特征函数所需的类型,无论需要什么子类型。

为什么会发生这种情况,主要是因为您实际上需要关联类型的定义,以便特征“完整”,以便编译器可以使用它。 Tim的答案解释了将定义放在链的更高位置(而不是Example内部)。


这并没有真正回答关于“为什么”的提问。 - Shepmaster
谢谢,我会更新,但它本质上是对第二个问题“如何让这段代码工作?”的回答。 - Seivan

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