[英]Why can't `&(?Sized + Trait)` be cast to `&dyn 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?如果我可以使用两者来调用 trait 方法,那么
&dyn Trait
和&(?Sized + Trait)
之间到底有什么区别?
A type implementing FooTraitContainerTrait
might eg have type Contained = dyn FooTrait
or type Contained = T
where T
is a concrete type that implements FooTrait
.实现
FooTraitContainerTrait
的类型可能例如具有type Contained = dyn FooTrait
或type Contained = T
,其中T
是实现FooTrait
的具体类型。 In both cases it's trivial to obtain a &dyn FooTrait
.在这两种情况下,获得
&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
?为什么在
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)
}
Uncommenting the foo_dyn(some_foo)
line results in the compiler error取消注释
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`
This problem can be reduced to the following simple example (thanks to turbulencetoo ):这个问题可以简化为以下简单示例(感谢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
is Sized
, the compiler knows statically what vtable it should use to create the trait object;T
是Sized
,编译器静态地知道它应该使用什么 vtable 来创建 trait 对象;T
is dyn Foo
, the vtable pointer is part of the reference and can just be copied to the output.T
是dyn Foo
,则 vtable 指针是引用的一部分,可以直接复制到输出。 But there's a third possibility that throws a wrench in the works:但是还有第三种可能性会影响工作:
T
is some unsized type that is not dyn Foo
, even though the trait is object safe, there is no vtable for impl Foo for T
.T
是一些不是dyn Foo
未定大小的类型,即使 trait 是对象安全的,也没有 vtable for impl Foo for T
。 The reason there is no vtable is because the vtable for a concrete type assumes that self
pointers are thin pointers.没有 vtable 的原因是因为具体类型的 vtable 假定
self
指针是细指针。 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.当你在一个
dyn Trait
对象上调用一个方法时,vtable 指针用于查找一个函数指针,并且只有数据指针被传递给函数。
However, suppose you implement a(n object-safe) trait for an unsized type:但是,假设您为未确定大小的类型实现了 (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 */}
}
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
.如果有这方面的一个虚函数表
impl
,那就得接受脂肪指针,因为impl
可以使用的方法Bar
这是在动态分派self
。
This causes two problems:这会导致两个问题:
Bar
vtable pointer in a &dyn Foo
object, which is only two pointers in size (the data pointer and the Foo
vtable pointer).&dyn Foo
对象中存储Bar
vtable 指针,该对象的大小只有两个指针(数据指针和Foo
vtable 指针)。 Therefore, even though dyn Bar
implements Foo
, it is not possible to turn a &dyn Bar
into a &dyn Foo
.因此,即使
dyn Bar
实现了Foo
,也不可能将&dyn Bar
变成&dyn Foo
。
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]
.尽管切片(另一种未调整大小的类型)没有使用 vtables 实现,但指向它们的指针仍然很胖,因此同样的限制适用
impl Foo for [i32]
。
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
".在某些情况下,您可以使用
CoerceUnsized
(仅在 Rust 1.36 中每晚使用)来表达诸如“必须&dyn FooTrait
为&dyn FooTrait
”之类的边界。 Unfortunately, I don't see how to apply this in your case.不幸的是,我不知道如何在您的情况下应用它。
str
) that cannot be coerced to a reference to a trait object. str
) 的引用,该类型不能被强制转换为对 trait 对象的引用。Not sure if that solves your concrete problem, but I did solve mine with the following trick:不确定这是否能解决您的具体问题,但我确实使用以下技巧解决了我的问题:
I added the following method to FooTrait
:我在
FooTrait
添加了以下方法:
fn as_dyn(&self) -> &dyn FooTrait;
A default impl can not be provided (because it requires that Self
be Sized
, but constraining FooTrait
to be Sized
forbids creating trait objects for it...).不能提供默认的 impl(因为它要求
Self
为Sized
,但将FooTrait
限制为Sized
禁止为其创建 trait 对象......)。
However, for all Sized
implementations, it is trivially implemented as然而,对于所有
Sized
实现,它被简单地实现为
fn as_dyn(&self) -> &dyn FooTrait { self }
So basically it constrains all implementations of FooTrait
to be sized, except for dyn FooTrait
.所以基本上它限制了
FooTrait
所有实现的大小,除了dyn FooTrait
。
Referenced from this blog , which explains the fat pointer really well.引用自此博客,它很好地解释了胖指针。
Thanks trentcl for simplifying the question to:感谢trentcl将问题简化为:
trait Foo {}
fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
arg
}
This brings to how to cast between different ?Sized
?这就带来了如何在不同的
?Sized
之间进行转换?
To answer this, let's first peek the implementation for Unsized type Trait
.为了回答这个问题,让我们先看看 Unsized 类型
Trait
的实现。
trait Bar {
fn bar_method(&self) {
println!("this is bar");
}
}
trait Foo: Bar {
fn foo_method(&self) {
println!("this is foo");
}
}
impl Bar for u8 {}
impl Foo for u8 {}
fn main() {
let x: u8 = 35;
let foo: &dyn Foo = &x;
// can I do
// let bar: &dyn Bar = foo;
}
So, can you do let bar: &dyn Bar = foo;
那么,你能不能
let bar: &dyn Bar = foo;
? ?
// below is all pseudo code
pub struct TraitObjectFoo {
data: *mut (),
vtable_ptr: &VTableFoo,
}
pub struct VTableFoo {
layout: Layout,
// destructor
drop_in_place: unsafe fn(*mut ()),
// methods shown in deterministic order
foo_method: fn(*mut ()),
bar_method: fn(*mut ()),
}
// fields contains Foo and Bar method addresses for u8 implementation
static VTABLE_FOO_FOR_U8: VTableFoo = VTableFoo { ... };
From the pseudo code, we can know从伪代码我们可以知道
// let foo: &dyn Foo = &x;
let foo = TraitObjectFoo {&x, &VTABLE_FOO_FOR_U8};
// let bar: &dyn Bar = foo;
// C++ syntax for contructor
let bar = TraitObjectBar(TraitObjectFoo {&x, &VTABLE_FOO_FOR_U8});
The bar
type is TraitObjectBar
, which is not the type TraitObjectFoo
. bar
类型是TraitObjectBar
,而不是TraitObjectFoo
类型。 That is to say, you cannot assign a struct of one type to another different type (in rust, in C++ you can use reinterpret_cast).也就是说,您不能将一种类型的结构分配给另一种不同的类型(在 rust 中,在 C++ 中您可以使用 reinterpret_cast)。
What you can do it to have another level of indirection :你可以做什么来获得另一个间接级别:
impl Bar for dyn Foo {
...
}
let bar: &dyn Bar = &foo;
// TraitObjectFoo {&foo, &VTABLE_FOO_FOR_DYN_FOO}
The same thing applies to Slice.同样的事情也适用于 Slice。
The workaround for casting different Unsized
can be done by this trick :铸造不同的
Unsized
的解决方法可以通过这个技巧来完成:
// blanket impl for all sized types, this allows for a very large majority of use-cases
impl<T: Bar> AsBar for T {
fn as_bar(&self) -> &dyn Bar { self }
}
// a helper-trait to do the conversion
trait AsBar {
fn as_bar(&self) -> &dyn Bar;
}
// note that Bar requires `AsBar`, this is what allows you to call `as_bar`
// from a trait object of something that requires `Bar` as a super-trait
trait Bar: AsBar {
fn bar_method(&self) {
println!("this is bar");
}
}
// no change here
trait Foo: Bar {
fn foo_method(&self) {
println!("this is foo");
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.