问题描述
在下面的代码中,无法通过对实现相同特征的动态大小类型的引用来获得对特征对象的引用.为什么会这样呢?如果我可以同时使用这两个方法来调用trait方法,那么&dyn Trait
和&(?Sized + 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
的类型例如具有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
是某种不是dyn Foo
的非大小型类型,即使该特征是对象安全的,也没有用于impl Foo for T
的vtable.
- 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
可能使用Bar
的方法,这些方法是在self
上动态调度.
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
.
这会导致两个问题:
- 在
&dyn Foo
对象中没有存储Bar
vtable指针的地方,该对象只有两个指针(数据指针和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中?
- 使用特征对象在rust中传递str 具有不能强制对特征对象的引用的对未定尺寸类型(
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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!