Reputation: 3862
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
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, ...>
toFoo<..., U, ...>
, when:
Foo
is a struct.T
implementsUnsize<U>
.- The last field of
Foo
has a type involvingT
.- If that field has type
Bar<T>
, thenBar<T>
implementsUnsized<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>
.
Upvotes: 2