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



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

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>的自动转换。

所以我的问题是:

  • 这里会发生某种转换吗?
  • 如果是这样,它背后的机制是什么,它是如何工作的?(我也对低级细节感兴趣,即如何在引擎盖下表示东西(
  • 有没有办法直接从i32创建Box<dyn Fooer>?如果没有:为什么不呢?

但是,变体 c 没有,可能是因为new函数仅接受相同类型的值,而自Fooer != i32年以来情况并非如此。

不,这是因为没有Box<dyn Fooer>new功能。在文档中:

impl<T> Box<T>

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

Box<T>上的大多数方法都允许T: ?Sized,但new是在没有T: ?Sized边界impl中定义的。这意味着仅当T是具有已知大小的类型时,才能调用Box::<T>::newdyn Fooer大小不大,因此根本没有要调用的new函数。

事实上,这个函数在今天的 Rust 中是不存在的Box<T>::new需要知道具体类型T,以便它可以分配正确大小和对齐方式的内存。因此,在将T发送到Box::new之前,您无法将其擦除。(可以想象,未来的语言扩展可能允许函数接受未调整大小的参数;但是,目前还不清楚即使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返回一个Box<i32>,然后被强制Box<Fooer>。您可以将其视为转换,但Box不会更改;编译器所做的只是在其上粘贴一个额外的指针并忘记其原始类型。罗德里戈的回答更详细地介绍了这种胁迫的语言层面机制。

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

有没有办法直接从i32创建Box<Fooer>

为"否":必须先将i32装箱,然后才能擦除其类型。这与你不能写let x: Fooer = 10i32的原因相同.

相关

  • 为什么我不能编写与 Box::new 类型相同的函数?
  • 是否允许多态变量?
  • 你如何在 Rust 中实际使用动态大小的类型?
  • 为什么禁止"let ref a: Trait = Struct"?

我将尝试解释代码中发生的转换(强制(。

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

取消大小适用于:

  • TT: TraitUnsize<Trait>.
  • [...]

这种特征AFAIK不直接用于胁迫。而是使用CoerceUnsized。这种特质在很多情况下都是实现的,其中一些是意料之中的,例如:

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>

关于你的最后一个问题:

有没有办法直接从i32创建Box<Fooer>?如果没有:为什么不呢?

不是我知道的。问题是Box::new(T)需要一个大小值,因为传递的值被移动到框中,而未调整大小的值无法移动。

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

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

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

最新更新