Changing type from Box<S> to Box<dyn T>

Hi, I am new to rust and I am trying to understand how the following snippet works:

let x: Box<&str> = Box::new("test");
let y: Box<dyn AsRef<str>> = x;

If I understand correctly, we get fat pointers in case of references. So for the following snippet:

let x: &str = "test";
let y: &dyn AsRef<str> = &x;

y is a fat pointer, which will have a pointer to x, and a pointer to vtable entry for as_ref. This is a concept in rust and the rust compiler handles it by appropriately creating a fat pointer for y.

But I don't get what happens in the case of Box. When I say let y: Box<dyn AsRef<str>> = x, I am assigning a box of 1 type to a variable which is a box of another type. Here y itself is not of a pointer type, it's a Box. The fat pointer will have to be inside the Box. But we are not asking rust to create a new Box type by calling Box::new or some other function that will convert x to a box of a fat pointer. The box is not directly a construct of a language like &, it's a Type defined by std. So how does rust know how to create a new box type which takes ownership of x, and uses a fat pointer. Is there a Trait or function that I am not aware of and is being called here implicitly?

TLDR: if I am writing an implementation, say MyBox, which is similar to Box, what would I have in MyBox, which will allow the above code to compile, and how would it work?

1 Like

This is a type of implicit coercion called an unsize coercion.

Note that Box<dyn Trait> is itself a fat pointer, just like &dyn Trait. (It's not a pointer to a boxed fat pointer.) For example, this program:

dbg!(size_of::<Box<&str>>());
dbg!(size_of::<Box<dyn AsRef<str>>>());

prints:

[src/main.rs:4] size_of::<Box<&str>>() = 8
[src/main.rs:5] size_of::<Box<dyn AsRef<str>>>() = 16

(Playground)

It's not possible in stable Rust. Standard library pointer types like Box and Rc can do it by implementing the unstable CoerceUnsized trait.

Thanks for the quick reply. Following your answer, I was able to find what I was looking for. So the coercion is done based on (currently unstable) trait CoerceUnsized. Conveniently the documentation also details how the new instance is created: The coercion will work by coercing the Bar<T> field into Bar<U> and filling in the rest of the fields from Foo<T> to create a Foo<U>

Box is a smart pointer. There are a few others. You can do this too, for example:

let x: Arc<&str> = Arc::new("test");
let y: Arc<dyn AsRef<str>> = x;

(Arc implements CoerceUnsized. The documentation there also refers to implementors as pointer types.)

Yup, I got that part. I was just confused about the unsized coercion. Just like how Deref trait helps treat a type as a smart pointer, I was not sure which trait is used in this case. There is a lot of "magic" that goes on in rust which relies on traits under the hood (like IntoIter in for loops, and Defer for smart pointers). So I was expecting a Trait to be responsible for unsized coercion as well, but I was not aware of the CoerceUnsized trait (or even coerce word here which would have helped me look this up quickly). I wish there was a simpler way to figure this out other than just going over the docs or asking around. I did try MIR in https://play.rust-lang.org/, but that didn't help.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.