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

问题描述

在下面的代码中,不可能从对实现相同特征的动态大小类型的引用中获取对特征对象的引用.为什么会这样?&dyn Trait&(?Sized + Trait) 如果我可以同时使用两者来调用 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 的类型可能例如have 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 是某种not dyn Foo 的无大小类型,即使特征是对象安全的,也没有用于 impl Foo for T.
  • 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> 可以使用在 self 上动态调度的 Bar 方法.

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.

这会导致两个问题:

  • 没有地方可以将 Bar vtable 指针存储在 &dyn Foo 对象中,该对象的大小只有两个指针(数据指针和 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