问题描述
在下面的代码中,不可能从对实现相同特征的动态大小类型的引用中获取对特征对象的引用.为什么会这样?&dyn Trait
和 &(?Sized + Trait)
如果我可以同时使用两者来调用 trait 方法,那么这两者究竟有什么区别?
In the code below it is not possible to obtain a reference to a trait object from a reference to a dynamically-sized type implementing the same trait. Why is this the case? What exactly is the difference between &dyn Trait
and &(?Sized + Trait)
if I can use both to call trait methods?
实现 FooTraitContainerTrait
的类型可能例如have type Contained = dyn FooTrait
或 type Contained = T
其中 T
是实现 FooTrait
的具体类型.在这两种情况下,获得 &dyn FooTrait
都是微不足道的.我想不出另一种不起作用的情况.为什么在 FooTraitContainerTrait
的一般情况下这不可能?
A type implementing FooTraitContainerTrait
might e.g. have type Contained = dyn FooTrait
or type Contained = T
where T
is a concrete type that implements FooTrait
. In both cases it's trivial to obtain a &dyn FooTrait
. I can't think of another case where this wouldn't work. Why isn't this possible in the generic case of 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)
}
取消注释 foo_dyn(some_foo)
行会导致编译器错误
Uncommenting the foo_dyn(some_foo)
line results in the compiler error
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`
推荐答案
这个问题可以简化为下面这个简单的例子(感谢 太湍流):
This problem can be reduced to the following simple example (thanks to turbulencetoo):
trait Foo {}
fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
arg
}
乍一看,正如您所观察到的,它确实应该可以编译:
At first glance, it really looks like this should compile, as you observed:
- 如果
T
是Sized
,编译器静态地知道它应该使用什么vtable来创建特征对象; - 如果
T
是dyn Foo
,则 vtable 指针是引用的一部分,可以直接复制到输出中.
- If
T
isSized
, the compiler knows statically what vtable it should use to create the trait object; - If
T
isdyn Foo
, the vtable pointer is part of the reference and can just be copied to the output.
但是还有第三种可能性会阻碍工作:
But there's a third possibility that throws a wrench in the works:
- 如果
T
是某种notdyn Foo
的无大小类型,即使特征是对象安全的,也没有用于impl Foo for T
.
- If
T
is some unsized type that is notdyn Foo
, even though the trait is object safe, there is no vtable forimpl Foo for T
.
没有 vtable 的原因是因为具体类型的 vtable 假定 self
指针是细指针.在dyn Trait
对象上调用方法时,vtable指针用于查找函数指针,只有数据指针传递给函数.
The reason there is no vtable is because the vtable for a concrete type assumes that self
pointers are thin pointers. When you call a method on a dyn Trait
object, the vtable pointer is used to look up a function pointer, and only the data pointer is passed to the function.
但是,假设您为未确定大小的类型实现了一个(n 对象安全)特征:
However, suppose you implement a(n object-safe) trait for an unsized type:
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
> 可以使用在 self
上动态调度的 Bar
方法.
If there were a vtable for this impl
, it would have to accept fat pointers, because the impl
may use methods of Bar
which are dynamically dispatched on self
.
这会导致两个问题:
- 没有地方可以将
Bar
vtable 指针存储在&dyn Foo
对象中,该对象的大小只有两个指针(数据指针和Foo
vtable 指针). - 即使您有两个指针,也不能将胖指针"vtable 与瘦指针"vtable 混合和匹配,因为它们必须以不同的方式调用.
- There's nowhere to store the
Bar
vtable pointer in a&dyn Foo
object, which is only two pointers in size (the data pointer and theFoo
vtable pointer). - Even if you had both pointers, you can't mix and match "fat pointer" vtables with "thin pointer" vtables, because they must be called in different ways.
因此,即使dyn Bar
实现了Foo
,也不可能将&dyn Bar
变成&dyn Foo
.
Therefore, even though dyn Bar
implements Foo
, it is not possible to turn a &dyn Bar
into a &dyn Foo
.
虽然切片(另一种无大小的类型)没有使用 vtables 实现,但指向它们的指针仍然很胖,所以同样的限制适用于 impl Foo for [i32]
.
Although slices (the other kind of unsized type) are not implemented using vtables, pointers to them are still fat, so the same limitation applies to impl Foo for [i32]
.
在某些情况下,您可以使用 CoerceUnsized
(仅在 Rust 1.36 的每晚)来表达必须强制&dyn FooTrait
"之类的界限.不幸的是,我不知道如何将其应用于您的案例.
In some cases, you can use CoerceUnsized
(only on nightly as of Rust 1.36) to express bounds like "must be coercible to &dyn FooTrait
". Unfortunately, I don't see how to apply this in your case.
- 什么是胖指针"?在 Rust 中?
- 使用 trait 对象在 rust 中传递 str 有一个不能被强制转换为对 trait 对象的引用的无大小类型 (
str
) 的引用的具体示例.
- What is a "fat pointer" in Rust?
- Use trait object to pass str in rust has a concrete example of a reference to an unsized type (
str
) that cannot be coerced to a reference to a trait object.
这篇关于为什么不能将 `&(?Sized + Trait)` 转换为 `&dyn Trait`?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!