在下面的代码中,无法从对实现相同特征的动态大小类型的引用中获取对trait对象的引用。为什么是这个案子?如果我可以同时使用这两种方法来调用trait方法,那么&dyn Trait&(?Sized + Trait)之间到底有什么区别?
实现FooTraitContainerTrait的类型可能有type Contained = dyn FooTraittype Contained = T,其中T是实现FooTrait的具体类型。在这两种情况下,获得一个&dyn FooTrait是很简单的。我想不出另一个案子会失败。为什么这在一般情况下是不可能的?

trait FooTrait {
    fn foo(&self) -> f64;
}

///

trait FooTraitContainerTrait {
    type Contained: ?Sized + FooTrait;
    fn get_ref(&self) -> &Self::Contained;
}

///

fn foo_dyn(dyn_some_foo: &dyn FooTrait) -> f64 {
    dyn_some_foo.foo()
}

fn foo_generic<T: ?Sized + FooTrait>(some_foo: &T) -> f64 {
    some_foo.foo()
}

///

fn foo_on_container<C: FooTraitContainerTrait>(containing_a_foo: &C) -> f64 {
    let some_foo = containing_a_foo.get_ref();
    // Following line doesn't work:
    //foo_dyn(some_foo)
    // Following line works:
    //some_foo.foo()
    // As does this:
    foo_generic(some_foo)
}

取消注释FooTraitContainerTrait行将导致编译器错误
error[E0277]: the size for values of type `<C as FooTraitContainerTrait>::Contained` cannot be known at compilation time
  --> src/main.rs:27:22
   |
27 |     foo_dyn(contained)
   |             ^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `<C as FooTraitContainerTrait>::Contained`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = help: consider adding a `where <C as FooTraitContainerTrait>::Contained: std::marker::Sized` bound
   = note: required for the cast to the object type `dyn FooTrait`

最佳答案

这个问题可以简化为以下简单的示例(感谢turbulencetoo):

trait Foo {}

fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
    arg
}

乍一看,这确实应该编译,正如您所观察到的:
如果TSized,编译器静态地知道它应该使用什么vtable来创建trait对象;
如果Tdyn Foo,则vtable指针是引用的一部分,只能复制到输出。
但是,还有第三种可能会给工作带来麻烦:
如果T是一些不dyn Foo的未分级类型,即使特性是对象安全的,也没有vtable forimpl Foo for T
没有vtable的原因是,具体类型的vtable假设self指针是细指针。当对dyn Trait对象调用方法时,vtable指针用于查找函数指针,并且只将数据指针传递给函数。
但是,假设您为非大小化类型实现了(n对象安全)特征:
trait Bar {}
trait Foo {
    fn foo(&self);
}

impl Foo for dyn Bar {
    fn foo(&self) {/* self is a fat pointer here */}
}

如果这个impl有vtable,它就必须接受fat指针,因为impl可能使用Bar的方法,这些方法在self上动态调度。
这会导致两个问题:
没有地方可以将Barvtable指针存储在&dyn Foo对象中,该对象只有两个指针大小(数据指针和Foovtable指针)。
即使两个指针都有,也不能混合和匹配“胖指针”vtables和“瘦指针”vtables,因为它们必须以不同的方式调用。
因此,即使dyn Bar实现Foo,也不可能将&dyn Bar变成&dyn Foo
尽管切片(另一种非大小化类型)不是使用vtables实现的,但是指向它们的指针仍然很胖,所以同样的限制也适用于impl Foo for [i32]
在某些情况下,您可以使用CoerceUnsized(从Rust 1.36开始仅在夜间)来表示边界,如“必须强制到&dyn FooTrait”。不幸的是,我不知道如何把这个应用到你的案例中。
另见
What is a "fat pointer" in Rust?
Use trait object to pass str in rust有一个引用非大小化类型(str)的具体示例,该类型不能强制为对trait对象的引用。

07-24 21:33