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

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

问题描述

我很难理解盒装特征的价值是如何形成的.考虑以下代码:

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::<Fooer>::new(32);           // doesn't work
    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>
}

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

所以我的问题是:

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

解决方案

但是,变体c却没有,这可能是因为new函数仅采用相同类型的值,而Fooer != i32以来却不是这种情况.

不,这是因为 没有功能.在文档:

impl<T> Box<T>

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

Box<T>上的大多数方法都允许T: ?Sized,但是new是在impl 中定义的,而没有 范围.表示手段,您只能在T为具有已知大小的类型. dyn Fooer没有大小,因此根本没有要调用的new方法.

实际上,该方法不存在.为了装箱,您需要知道它的大小.为了将其传递给函数,您需要知道其大小.为了使变量包含某些内容,它必须具有大小.像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不变.编译器所做的全部工作就是在其上附加一个指针,而忘记了其原始类型. 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::<Fooer>::new(32);           // doesn't work
    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>
}

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<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<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 method to call.

In fact, that method can't exist. In order to box something, you need to know its size. In order to pass it to a function, you need to know its size. In order to even have a variable containing something, it must have a size. 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天全站免登陆