问题
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 ani32
? 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 sinceFooer != 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 ani32
?
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
isUnsize<Trait>
whenT: 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 ani32
? 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