创建盒装特征背后的机制如何运作? [英] How does the mechanism behind the creation of boxed traits work?

查看:14
本文介绍了创建盒装特征背后的机制如何运作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

trait Fooer {fn foo(&self);}i32 的 impl Fooer {fn foo(&self) { println!("Fooer on i32!");}}fn 主要() {让 a = Box::new(32);//工作,创建一个 Box<i32>让 b = Box::<i32>::new(32);//工作,创建一个 Box<i32>让 c = Box::::new(32);//不起作用let d: Box<dyn Fooer>=框::新(32);//工作,创建一个 Box<Fooer>let e: Box<dyn Fooer>= 框::<i32>::new(32);//工作,创建一个 Box<Fooer>}

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

所以我的问题是:

  • 这里是否发生了某种转换?
  • 如果是这样,它背后的机制是什么,它是如何工作的?(我也对底层细节感兴趣,即在幕后如何表示东西)
  • 有没有办法直接从 i32 创建一个 Box?如果没有:为什么不呢?

解决方案

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

不,这是因为 没有 Boxnew 函数.在文档中:

<块引用>

实现<T>框<T>

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

Box<T> 上的大多数方法都允许 T: ?Sized,但 new 是在 impl 没有 T: ?Sized 绑定.那意味着你只能调用Box::<T>::newT 是一个已知大小的类型.dyn Fooer 没有大小,因此根本没有要调用的 new 函数.

事实上,这个函数不能存在于今天的 Rust 中.Box<T>::new 需要知道具体类型 T 以便它可以分配正确大小和对齐的内存.因此,您不能在将其发送到 Box::new 之前删除 T .(可以想象未来的语言扩展 可能允许函数接受未调整大小的参数;但是,甚至 unsized_locals 是否真的会启用 Box<T>::new 以接受未调整大小的 T.)

目前,像 dyn Fooer 这样的无大小类型只能存在于胖指针"之后,即指向对象的指针指向对象的指针该对象的 Fooer 的实现.你如何得到一个胖指针?你从一个细指针开始并强制它.这就是这两行发生的事情:

let d: Box=框::新(32);//工作,创建一个 Box<Fooer>let e: Box<Fooer>= 框::<i32>::new(32);//工作,创建一个 Box<Fooer>

Box::new 返回一个 Box<i32>,即 强制Box<Fooer>.您可以认为这是一种转换,但 Box 并没有改变;编译器所做的只是在它上面加上一个额外的指针并忘记它的原始类型.rodrigo 的回答更详细地介绍了这种强制的语言级机制.

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

<块引用>

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

是否":i32 必须被装箱你可以删除它的类型.这与你不能写 let x: Fooer = 10i32 的原因相同.

相关

I'm having trouble understanding how values of boxed traits come into existence. Consider the following code:

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>
}

Obviously, variant a and b work, trivially. 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. Variant d and e work, which lets me suspect that some kind of automatic conversion from Box<i32> to Box<dyn Fooer> is being performed.

So my questions are:

  • Does some kind of conversion happen here?
  • If so, what the mechanism behind it and how does it work? (I'm also interested in the low level details, i.e. how stuff is represented under the hood)
  • Is there a way to create a Box<dyn Fooer> directly from an i32? If not: why not?

解决方案

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.

No, it's because there is no new function for Box<dyn Fooer>. In the documentation:

impl<T> Box<T>

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

Most methods on Box<T> allow T: ?Sized, but new is defined in an impl without a T: ?Sized bound. That means you can only call Box::<T>::new when T is a type with a known size. dyn Fooer is unsized, so there simply isn't a new function to call.

In fact, that function can't exist in today's Rust. Box<T>::new needs to know the concrete type T so that it can allocate memory of the right size and alignment. Therefore, you can't erase T before you send it to Box::new. (It's conceivable that future language extensions may allow functions to accept unsized parameters; however, it's unclear whether even unsized_locals would actually enable Box<T>::new to accept unsized T.)

For the time being, unsized types like dyn Fooer can only exist behind a "fat pointer", that is, a pointer to the object and a pointer to the implementation of Fooer for that object. How do you get a fat pointer? You start with a thin pointer and coerce it. That's what's happening in these two lines:

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>, which is then 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. rodrigo's answer goes into more detail about the language-level mechanics of this coercion.

Hopefully all of this goes to explain why the answer to

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

is "no": the i32 has to be boxed before you can erase its type. It's the same reason you can't write let x: Fooer = 10i32.

Related

这篇关于创建盒装特征背后的机制如何运作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆