amin
amin

Reputation: 3862

How does Rust handle converting &SizedType to &UnsizedType?

struct MaybeSized<T: ?Sized> {
    v: T,
}

fn main() {
    let sized = MaybeSized {
        v: "sized".to_string(),
    };

    use std::fmt::Display;

    {
        // what exactly happens here?
        let ref1: &MaybeSized<Display> = &sized;
    }
    {
        // why this fails to compile?
        let ref2: &MaybeSized<str> = &sized;
    }
}

MaybeSize<String> is a sized type; what is on the stack and heap when ref1 : &MaybeSized<Display>) is created? Why this "magic" doesn't work for another unsized type like MaybeSized<str>?

Upvotes: 1

Views: 230

Answers (1)

Sven Marnach
Sven Marnach

Reputation: 602605

The conversion from &MaybeSized<String> to &MaybeSized<dyn Display> is called an unsized coercion. Concrete types can be coerced into trait objects for the traits they implement, and this coercion extends to a generic struct under certain conditions:

Foo<..., T, ...> to Foo<..., U, ...>, when:

  • Foo is a struct.
  • T implements Unsize<U>.
  • The last field of Foo has a type involving T.
  • If that field has type Bar<T>, then Bar<T> implements Unsized<Bar<U>>.
  • T is not part of the type of any other fields.

(For the full details follow the link to the language reference above.)

The variable sized is stored on the stack, but the data of the String is allocated on the heap. The reference ref1 is stored on the stack as a fat pointer – a pointer to sized together with a pointer to the virtual method table for String as dyn Display to allow dynamically calling the right methods if necessary.

This doesn't work for MaybeSized<str> because there is no unsized coercion that converts to str. You can convert and &String to a &str using a deref coercion, but that's not what we need here – unsizing a sized type requires an unsized coercion. The unsized type MaybeSized<str> consists of the actual string data, while MaybeSized<String> consists of a length, a capacity and a pointer to the heap, so there is no way to make the memory layouts match.

There are other cases that work, though, e.g.

let a = MaybeSized { v: [65u8, 66, 67]};
let b: &MaybeSized<[u8]> = &a;

works fine, since there is an unsized coercion from [u8; 3] to [u8]. With unsafe code you can convert this to &MaybeSized<str> if you really want to:

let c: &MaybeSized<str> = unsafe { &*(b as *const _ as *const _) };

I can't think of a safe way of creating a &MaybeSized<str>.

(Code on the playground)

Upvotes: 2

Related Questions