为什么不能将&&(Sized + Trait)强制转换为&dyn Trait?

w1t*_*am3 5 generics polymorphism rust trait-objects

在下面的代码中,不可能从对实现相同特征的动态大小类型的引用中获得对特征对象的引用。为什么会这样呢?究竟是什么区别&dyn Trait&(?Sized + Trait)我是否可以使用这两种调用方法特质?

实现的类型FooTraitContainerTrait可能例如具有type Contained = dyn FooTraittype Contained = T在哪里T实现的具体类型FooTrait。在这两种情况下,都很难获得&dyn FooTrait。我想不出另一种情况,这是行不通的。为什么在通用情况下这不可能FooTraitContainerTrait

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

取消注释该foo_dyn(some_foo)行会导致编译器错误

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

tre*_*tcl 16

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

trait Foo {}

fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
    arg
}
Run Code Online (Sandbox Code Playgroud)

乍一看,正如您所观察到的,它确实应该可以编译:

  • 如果TSized,编译器静态地知道它应该使用什么 vtable 来创建 trait 对象;
  • 如果Tdyn Foo,则 vtable 指针是引用的一部分,可以直接复制到输出。

但是还有第三种可能性会影响工作:

  • IfT是一些 unsized 类型不是 dyn Foo,即使 trait 是对象安全的,也没有 vtable for impl Foo for T

没有 vtable 的原因是因为具体类型的 vtable 假定self指针是细指针。在dyn Trait对象上调用方法时,vtable 指针用于查找函数指针,只有数据指针传递给函数。

但是,假设您为未确定大小的类型实现了 (n object-safe) trait:

trait Bar {}
trait Foo {
    fn foo(&self);
}

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

如果这样做的一个虚函数表impl,它必须接受脂肪指针,因为impl可以使用的方法,Bar这对动态调度self

这会导致两个问题:

  • 没有地方可以Bar&dyn Foo对象中存储vtable 指针,对象的大小只有两个指针(数据指针和Foovtable 指针)。
  • 即使您有两个指针,也不能将“胖指针”vtable 与“瘦指针”vtable 混合和匹配,因为它们必须以不同的方式调用。

因此,即使dyn Bar实现了Foo,也不可能将 a&dyn Bar变成 a &dyn Foo

尽管切片(另一种未调整大小的类型)不是使用 vtables 实现的,但指向它们的指针仍然很胖,因此同样的限制适用于impl Foo for [i32].

在某些情况下,您可以使用CoerceUnsized(仅从 Rust 1.36 开始每晚使用)来表达“必须强制到&dyn FooTrait”之类的界限。不幸的是,我不知道如何在您的情况下应用它。

也可以看看