This is part 1 of my thoughts on various union-related topics. It's not really unsafe code guidelines related and there is not even very much to explain, so I figured I'd just post it here as some form of pre-RFC.
Summary
Define ManuallyDrop
as
#[repr(transparent)]
struct ManuallyDrop<T : ?Sized>(T);
but also make it a lang-item to get the magic behavior of not calling Drop
on its content. IOW, ManuallyDrop<T>
never has drop glue. In every other aspect ManuallyDrop
behaves like a normal struct
, including the way DST work and layout optimizations.
With unions and ManuallyDrop
disentangled, there is no reason to allow fields with drop glue in unions at all. So weaken the current check on stable (fields must be Copy
) to test if the type does not need drop glue, and we remove the unstable feature to bypass this check. That may not always be possible when generic types are involved; we can then fall back to ensure that types are Copy
because such types never have drop glue. (I think there will not be many false positives in this test: We can traverse through any types that are statically given, stop at things like &
, &mut
, ManuallyDrop
, raw pointers and other type constructors that never have drop glue even if their type parameter does, and then only check for Copy
when hitting a generic or associated type [constructor].)
Notably, ManuallyDrop
never has drop glue and hence can be used to put any type into a union. For example, MaybeUninit
remains definable for all sized T
as
union MaybeUninit<T> {
uninit: (),
value: ManuallyDrop<T>, // we statically know this has no drop glue, even if T does
}
Then entirely remove support for union fields that do have drop glue. unions are no longer "magic" for dropping, the fact that they do nothing when dropped just follows from the fact that none of their fields would do anything. Writing to a union field will also never implicitly call drop
and hence can always be a safe operation.
However, one can of course still manually impl Drop for MyUnion
. This requires tracking whether or not a union is initialized (to decide whether we insert drop
when it goes out of scope). How exactly that is done is left as an open question.
This fixes #47034, resolves #49056 (which only got closed as "postponed") and makes most of the complexity that has been proposed in RFC #1897 unnecessary.
Some more details
There's no that much more to add (unless I missed something, which I probably did). The core observation is that once we have ManuallyDrop
, we can side-step all the complications around drop
in unions by just ruling out types that have drop glue. Some very complex rules have been proposed for when to drop a union field and when not to, and working with such unions is a huge footgun because it is hard to see from the code when something is dropped.
Instead, I propose we force people to write ManuallyDrop
, so drop is never called automatically and the programmer can manually take care of calling drop
when necessary. The fact that the type of the field is ManuallyDrop
should be a strong reminder that drop has to be manually handled, so this is much better than just silently not dropping fields in unions. (This actually happens currently to some extend: Fields are dropped on assignment but not when the union itself is dropped. So this proposal also removes some inconsistent union behavior.)
This should be fully backwards compatible as we can re-implement the API surface of ManuallyDrop
for the struct
, and union fields with drop glue have not been stabilized.
It is worth mentioning that @kennytm has a draft proposal for better Drop
control that would actually make ManuallyDrop
as described above 100% a library type:
Allowing user to redefine needs_drop also let us to implement ManuallyDrop ([RFC #1860]) with an unsized struct
Doing that later is backwards compatible with this proposal (we'd just remove the lang-item).
Open questions
I suppose a union implementing Drop
should have restrictions similar to struct
concerning moving out of fields and partial initialization?
What do you think? I am particularly interested in whether not allowing Drop
fields for union (but allowing ManuallyDrop<T>
for any T
because that never drops) would actually work for everyone. Are there cases where the extra wrapper of a ManuallyDrop
causes problems?
This does deliberately not answer any questions around DSTs in unions, union layout optimizations or other kind of question related to "which values are valid in a union". I think those are a separate concern, and we can now have ManuallyDrop
for DST without answering these questions.