简体   繁体   English

为什么不能将 `&(?Sized + Trait)` 转换为 `&dyn Trait`?

[英]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 FooTraittype 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:乍一看,正如您所观察到的,它确实应该可以编译:

  • If T is Sized , the compiler knows statically what vtable it should use to create the trait object;如果TSized ,编译器静态地知道它应该使用什么 vtable 来创建 trait 对象;
  • If T is dyn Foo , the vtable pointer is part of the reference and can just be copied to the output.如果Tdyn Foo ,则 vtable 指针是引用的一部分,可以直接复制到输出。

But there's a third possibility that throws a wrench in the works:但是还有第三种可能性会影响工作:

  • If 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:这会导致两个问题:

  • 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 the Foo vtable pointer).没有地方可以在&dyn Foo对象中存储Bar vtable 指针,该对象的大小只有两个指针(数据指针和Foo vtable 指针)。
  • 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.即使您有两个指针,也不能将“胖指针”vtable 与“瘦指针”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.不幸的是,我不知道如何在您的情况下应用它。

See also也可以看看

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(因为它要求SelfSized ,但将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

Try it on the playground 在操场上试试

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.

相关问题 为什么我不能用 let _: Arc 创建一个特征 object<dyn trait> = value.into()?</dyn> - Why can't I create a trait object with let _: Arc<dyn Trait> = value.into()? 为什么特征中的泛型方法需要调整特征对象的大小? - Why does a generic method inside a trait require trait object to be sized? 我如何施放 Rc <refcell<concretetype> &gt; 到 Rc <refcell<dyn trait> &gt;? </refcell<dyn></refcell<concretetype> - How do I cast Rc<RefCell<ConcreteType>> to Rc<RefCell<dyn Trait>>? 为什么我不能在带有类型参数的特征上添加一个全面的实现? - Why can't I add a blanket impl on a trait with a type parameter? 使用需要大小的特征对象 - Working with trait objects requiring sized Trait:: 之间有什么区别?<T> 和<Trait<T> &gt;? - What is the difference between Trait::<T> and <Trait<T>>? 无法理解为什么`trait Hello [+ A] {def test [B &lt;:A]}`无法编译 - Can't understand why `trait Hello[+A] { def test[B<:A] }` can't be compiled 为什么忽略“按特征发送”实现的特征范围? - Why are trait bounds for Send on trait implementations ignored? rust 具有不同泛型类型的新动态特征变量? - rust dyn trait variable new with different generic types? 它是一种书写约定吗?作为一个特征绑定本身的大小? - Is it a convention to write ?Sized by itself as a trait bound?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM