在下面的代码中,无法从对实现相同特征的动态大小类型的引用中获取对trait对象的引用。为什么是这个案子?如果我可以同时使用这两种方法来调用trait方法,那么&dyn Trait
和&(?Sized + Trait)
之间到底有什么区别?
实现FooTraitContainerTrait
的类型可能有type Contained = dyn FooTrait
或type 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
}
乍一看,这确实应该编译,正如您所观察到的:
如果
T
是Sized
,编译器静态地知道它应该使用什么vtable来创建trait对象;如果
T
是dyn 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
上动态调度。这会导致两个问题:
没有地方可以将
Bar
vtable指针存储在&dyn Foo
对象中,该对象只有两个指针大小(数据指针和Foo
vtable指针)。即使两个指针都有,也不能混合和匹配“胖指针”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对象的引用。