Since enums can be encoded as a struct with a tag and a union, and structs support DSTs in their last field, wouldn’t such a proposal also effectively introduce enums with unsized fields? (In which case they could also be introduced properly... well, maybe enums are more complicated due to things like niche optimization, so let’s not focus on them too much.)
What’s your take on unsized coercions? If I have a
union Either<Left: ?Sized, Right: ?Sized> {
left: ManuallyDrop<Left>,
right: ManuallyDrop<Right>,
}
can I turn Either<[bool; 10], i32>
into Either<[bool], dyn Any>
? (Probably not, how is Rust supposed to know which one of the vtables to include..)
For the use-case of MaybeUninit
you’d only need support for a single T: ?Sized
argument, right? I think things only become complicated once you want to support multiple different parameters. I guess, in general, there are union fields that are
- always
Sized
,
- always
!Sized
and their pointer metadata comes from some fixed type dyn Trait
or [Type]
- potentially unsized, depending on a type parameter
T
, where T: ?Sized
and the type of the pointer metadata comes directly from T
; or
- potentially unsized, depending one or multiple parameters, but through some indirection (e.g. associated type of traits, etc.)
For structs, similar cases are possible for the last field, but the only really interesting/useful unsized case is just the 3rd one, since otherwise it’s hard to construct the struct anyways (without lots of unsafe code).
Focusing on this case then, we might restrict unsized unions to cases where where every field is either
or
- potentially using metadata depending directly from a type parameter
T
with the additional restrictions that
-
all of the potentially unsized fields use the same type parameter
T
; and
-
the type parameter
T
is not used in any of the always sized fields.
This includes MaybeUninit
. AFAICT, in these cases unions should then be able to support Unsize
.
Going back to the cases 2. or 4. above; I haven’t thought much about 4., but regarding 2., one might also allow unions where all fields are either always sized or always unsized and all of the metadata types are the same. This kind of union could then, perhaps, be constructed by transmuting a (similarly structured) union that’s of the form described in the previous section. However it is debatable how valuable such unions are after all. For structs, the possibility to do something like creating a newtype around e.g. str
is a good use-case; on the other hand a union like
union Foo {
x: str,
y: (u8, str),
}
could always be replaced by something like this using a struct:
union FooInner<T: ?Sized> {
x: ManuallyDrop<T>,
y: ManuallyDrop<(u8, T)>,
}
struct Foo {
inner: FooInner<str>
}
Edit: Some more examples on the restrictions I tried to lay out above:
// allowed
union MaybeUninit<T: ?Sized> {
uninit: (),
value: ManuallyDrop<T>,
}
// allowed
union FooInner<T: ?Sized> {
x: ManuallyDrop<T>,
y: ManuallyDrop<(u8, T)>,
}
// not allowed
union Bar<T: ?Sized> {
x: ManuallyDrop<Box<T>>,
y: ManuallyDrop<T>,
}
// allowed
union Bar<S: ?Sized, T: ?Sized> {
x: ManuallyDrop<Box<S>>,
y: ManuallyDrop<T>,
}
// not allowed
union Bar<S: ?Sized, T: ?Sized> {
x: ManuallyDrop<S>,
y: ManuallyDrop<T>,
}