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

孤者浪人 提交于 2019-12-18 06:54:06

问题


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?

回答1:


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

  • Why can't I write a function with the same type as Box::new?
  • Are polymorphic variables allowed?
  • How do you actually use dynamically sized types in Rust?
  • Why is `let ref a: Trait = Struct` forbidden?



回答2:


I'll try to explain what conversions (coercions) happen in your code.

There is a marker trait named Unsize that, between others:

Unsize is implemented for:

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

This trait, AFAIK, is not used directly for coercions. Instead, CoerceUnsized is used. This trait is implemented in a lot of cases, some of them are quite expected, such as:

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

that is used to coerce &i32 into &Fooer.

The interesting, not so obvious implementation for this trait, that affects your code is:

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

This, together with the definition of the Unsize marker, can be somewhat read as: if U is a trait and T implements U, then Box<T> can be coerced into Box<U>.

About your last question:

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

Not that I know of. The problem is that Box::new(T) requires a sized value, since the value passed is moved into the box, and unsized values cannot be moved.

In my opinion, the easiest way to do that is to simply write:

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

That is, you create a Box of the proper type and then coerce to the unsized one (note it looks quite similar to your d example).



来源:https://stackoverflow.com/questions/52288980/how-does-the-mechanism-behind-the-creation-of-boxed-traits-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!