在下面的代码中,无法通过对实现相同特征的动态大小类型的引用获得对特征对象的引用。为什么会这样呢?如果&dyn Trait
和&(?Sized + Trait)
可以同时调用特征方法,到底有什么区别?
实现FooTraitContainerTrait
的类型例如具有type Contained = dyn FooTrait
或type 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)
}
取消注释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`
答案 0 :(得分:3)
此问题可以简化为以下简单示例(感谢turbulencetoo):
trait Foo {}
fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
arg
}
乍一看,就像您观察到的那样,看起来确实应该编译:
T
是Sized
,则编译器静态地知道应使用哪个vtable创建特征对象; T
是dyn Foo
,则vtable指针是引用的一部分,可以直接复制到输出中。但是还有第三种可能性在工作中投入扳手:
T
是不是 dyn Foo
的某种大小不定的类型,即使该特征是对象安全的,也没有impl Foo for T
的vtable。没有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
有一个 vem表,则它必须接受 fat </ em>指针,因为impl
可以使用{ {1}}会在Bar
上动态调度。
这会导致两个问题:
self
对象中没有存储Bar
vtable指针的对象,该对象只有两个指针(数据指针和&dyn Foo
vtable指针)。因此,即使Foo
实现了dyn Bar
,也无法将Foo
变成&dyn Bar
。
尽管未使用vtables实现切片(另一种未调整大小的类型),但指向它们的指针仍然很胖,因此对&dyn Foo
也有相同的限制。
在某些情况下,您可以使用CoerceUnsized
(仅在Rust 1.36起每晚使用)来表示“必须对impl Foo for [i32]
强制”的边界。不幸的是,我不知道如何在您的情况下应用此功能。
&dyn FooTrait
)的引用。答案 1 :(得分:2)
不确定是否能解决您的具体问题,但是我确实通过以下技巧解决了我的问题:
我向FooTrait
添加了以下方法:
fn as_dyn(&self) -> &dyn FooTrait;
无法提供默认的impl(因为它要求Self
为Sized
,但是将FooTrait
约束为Sized
则禁止为其创建特征对象... )。
但是,对于所有Sized
实现,它都是简单实现的
fn as_dyn(&self) -> &dyn FooTrait { self }
因此,基本上,它约束了FooTrait
的所有实现的大小,除了dyn FooTrait
之外。
答案 2 :(得分:1)
此blog的参考,它很好地解释了胖指针。
感谢trentcl将问题简化为:
trait Foo {}
fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
arg
}
这带来了如何在不同的?Sized
之间进行投射?
要回答这个问题,让我们首先看一下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;
}
那么,你可以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 { ... };
通过伪代码,我们可以知道
// 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});
bar
类型为TraitObjectBar
,而不是TraitObjectFoo
类型。也就是说,您不能将一种类型的结构分配给另一种不同的类型(在rust中,在C ++中,您可以使用reinterpret_cast)。
具有另一种间接访问级别:
impl Bar for dyn Foo {
...
}
let bar: &dyn Bar = &foo;
// TraitObjectFoo {&foo, &VTABLE_FOO_FOR_DYN_FOO}
Slice也是如此。
此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");
}
}