本文介绍了为什么不能将&(Sized + Trait)强制转换为& dyn Trait?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的代码中,无法通过对实现相同特征的动态大小类型的引用来获得对特征对象的引用.为什么会这样呢?如果我可以同时使用这两个方法来调用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 FooTraittype 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:

  • 如果TSized,则编译器静态地知道应使用哪个vtable创建特征对象;
  • 如果Tdyn Foo,则vtable指针是引用的一部分,可以直接复制到输出中.
  • If T is Sized, the compiler knows statically what vtable it should use to create the trait object;
  • If T is dyn 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 not dyn Foo, even though the trait is object safe, there is no vtable for impl 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 the Foo 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.

  • 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.

这篇关于为什么不能将&amp;(Sized + Trait)强制转换为&amp; dyn Trait?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-11 22:50