盒装特征创建背后的机制如何运作?

How does the mechanism behind the creation of boxed traits work?

我无法理解盒装特征的值是如何产生的。考虑以下代码:

trait Fooer {
    fn foo(&self);
}

impl Fooer for i32 {
    fn foo(&self) { println!("Fooer on i32!"); }
}

fn main() {
    let a = Box::new(32);                        // works, creates a Box<i32>
    let b = Box::<i32>::new(32);                 // works, creates a Box<i32>
    let c = Box::<dyn Fooer>::new(32);           // doesn't work
    let d: Box<dyn Fooer> = Box::new(32);        // works, creates a Box<Fooer>
    let e: Box<dyn Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}

显然,变体 a 和 b 可以正常工作。但是,变体 c 没有,可能是因为 new 函数仅采用相同类型的值,而自 Fooer != i32 以来情况并非如此。变体 d 和 e 工作,这让我怀疑正在执行某种从 Box<i32>Box<dyn Fooer> 的自动转换。

所以我的问题是:

我将尝试解释您的代码中发生了哪些转换(强制转换)。

有一个名为 Unsize 的标记特征,在其他特征之间:

Unsize is implemented for:

  • T is Unsize<Trait> when T: Trait.
  • [...]

据我所知,这个特征不直接用于强制转换。相反,使用 CoerceUnsized。这个trait在很多情况下都实现了,其中有一些是意料之中的,比如:

impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T 
where
    'b: 'a,
    T: Unsize<U> + ?Sized,
    U: ?Sized

用于将&i32强制转换为&Fooer

此特征的有趣但不那么明显的实现会影响您的代码:

impl<T, U> CoerceUnsized<Box<U>> for Box<T> 
where
    T: Unsize<U> + ?Sized,
    U: ?Sized

这与 Unsize 标记的定义一起,可以理解为:如果 U 是特征并且 T 实现 U,那么Box<T>可以强制转换为Box<U>.

关于你的最后一个问题:

Is there a way to create a Box<Fooer> directly from an i32? If not: why not?

据我所知没有。问题是 Box::new(T) 需要一个大小的值,因为传递的值被移动到框中,而未大小的值不能移动。

在我看来,最简单的方法就是简单地写:

let c = Box::new(42) as Box<Fooer>;

也就是说,您创建了一个正确类型的 Box,然后强制转换为未调整大小的类型(注意它看起来与您的 d 示例非常相似)。

However, variant c does not, probably because the new function takes only values of the same type which is not the case since Fooer != i32.

没有,因为没有Box<dyn Fooer>new功能。在 documentation:

impl<T> Box<T>

pub fn new(x: T) -> Box<T>

Box<T> 上的大多数方法允许 T: ?Sized,但是 new 是在 impl 中定义的 T: ?Sized绑定。当 T 是已知大小的类型时, 只能调用 Box::<T>::newdyn Fooer 未调整大小,因此根本没有要调用的 new 函数。

事实上,那个函数 不能 存在于今天的 Rust 中。 Box<T>::new 需要知道具体类型 T 以便它可以分配正确大小和对齐的内存。因此,在发送到Box::new之前,你不能擦除T。 (可以想象 future language extensions 可能允许函数接受未调整大小的参数;但是,尚不清楚甚至 unsized_locals 是否真的会启用 Box<T>::new 接受未调整大小的 T。)

目前,像dyn Fooer这样的未确定大小的类型只能存在于“胖指针”后面,即指向对象的指针指向对象的指针该对象的 Fooer 的实现。你如何获得胖指针?你从一个细指针开始,强制它。这就是这两行中发生的事情:

let d: Box<Fooer> = Box::new(32);        // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>

Box::new returns a Box<i32>,然后 coerced to Box<Fooer>. You could consider this a conversion, but the Box isn't changed; all the compiler does is stick an extra pointer on it and forget its original type. 详细介绍了这种强制转换的语言级机制。

希望所有这些都能解释为什么

Is there a way to create a Box<Fooer> directly from an i32?

是“否”:i32 必须先装箱 ,然后 才能擦除其类型。这和你不能写 let x: Fooer = 10i32.

的原因是一样的

相关

  • How do you actually use dynamically sized types in Rust?
  • Why is `let ref a: Trait = Struct` forbidden?