The compiler understands that if an enum variant only contains an uninhabited type the variant cannot be constructed (if it's one of two variants of an enum, the discriminant will be elliminated). Existence of other fields, however, breaks this. Consider this type:
enum Test<T> {
One,
Two { large: [u8; 2048], never: T },
}
The size of Test<Infallible> and Test<!> (with the feature enabled) is 2049 (meaning it reserves space for both the data carried by the variant Two, and a byte for the discriminant), while it could be 0. Additionally, any structures containing an uninhabited type should be zero-sized, which is not the case.
Is this a feature request, or should I open an issue in the Rust repository under some other category?
This has been discussed, and is by design -- it's not changing.
In short, the problem is that even though (u64, !) is uninhabited, MaybeUninit<(u64, !)>is inhabited. And it's been decided that preserving that size+align equality is more important that letting structs like that be zero-size -- especially for unsafe code that does projections.
After all, any codepath that would have a valid(u64, !) is necessarily dead anyway.
well, Rust seems likely to gain support for constructing enums in a similar fashion, by writing each field to a MaybeUninit<TheEnum<T>> and then setting the discriminant. That would ran into the exact same issue if Rust treated uninhabited variants as zero-size.
I don't think there's any pre-existing differences between a type with a generic and an otherwise identical type without a generic, but it seems like it could be done in such a way that any non-generic struts be ZST. It would be odd, admittedly, but in theory it could work?
Good observation. I’ve left a note in the tracking issue for offset_of! on enums, because I couldn’t find any mention of uninhabited types around the whole topic, or any of the documentation.
There is, in one corner case: ?Sized generics must always be the tail field, and must always be laid out last in memory, to support unsizing coercions from &Struct<T> to &Struct<dyn Trait>. AIUI layout for sized T is functionality identical to the non-generic equivalent (but this is not guaranteed, of course).
But macros handling in-place construction might not be using generics for the uninhabited type, it might be e.g. a derive on a non-generic type.
Also, tuples as used in the OP are a generic type.
I think that particular example is more important to that discussion, as MaybeUninit<S<!>> is not a product type, but a sum type - MaybeUninit is a union. Or we may consider optimizing enums but not unions?
Yes, exactly. Unions can’t be optimized, as too many details are already stably guaranteed. (For more than 5 years already) you don’t even need unsafe to partially initialize something in a union
union Foo {
unit: (),
other: (u8, Infallible),
}
fn main() {
let mut x = Foo { unit: () };
x.other.0 = 42;
}