Pre-RFC (?): unions, drop types and `ManuallyDrop`

@nikomatsakis A GC will defer dropping an object until an arbitrary point in the future. For this to be safe, either the object must have no destructor, or the object must be 'static and therefore its destructor will only access owned and global data.

OK, I see. Still, 'static doesnā€™t seem to suffice to prevent that sort of problem completely. For example, if I have a Gc<Foo> and Foo has a Vec<Gc<Foo>>, I can build up a cycle for which there is no valid order in which to freeā€¦? Anyway, off topic.

1 Like

Isn't the requirement that the type has no drop glue rather than that it is non-Drop? Any type that contains a type that implements Drop will need drop glue to be run but is not itself Drop.

In this case the magical ManuallyDrop<T> struct will have a lang-item on it that instructs the compiler to never produce drop glue for it no matter what T is?

Yes, that's what I mean. But you are right that types can be Drop without doing impl Drop for T, so my terminology was not chosen very well.

Yes, that's exactly the point of ManuallyDrop (which already exists, I just propose to change its implementation).

1 Like

This is very pedantic, but unfortunately the language semantics are confusing enough that we should try to be maximally precise with our terminology. When we say "type is SomeTrait", that is generally colloquial for "the bound TheType: SomeTrait holds", but for types that have drop glue but no explicit implementation of Drop, that bound doesn't hold, so it's misleading to say e.g. "(String,) is Drop".

2 Likes

I agree. Terminology is important.

I will edit the first post to speak about (not) having drop glue instead.

2 Likes

We now have needs_drop, so maybe thereā€™s a phrasing something like that that could be used.

Weā€™d kind of need the inverse of needs_drop as a trait bound.

I think the code @m4b wants to write is something like below:

match my_union.some_drop {
    Some(WithDrop(..)) => { .. }
    ..
}

IMO, defining ManuallyDrop as pub struct ManuallyDrop<T>(pub T); should be enough for this case.

@hyeonu

You mean this?

match my_union.some_drop {
    Some(ManuallyDrop(..)) => { .. }
    ..
}

Given that accessing the field of a ManuallyDrop is safe and that it implements Deref and DerefMut, I suppose we could allow this.

Would this work?

// core::mem
pub unsafe auto trait TrivialDrop {}
impl<D: Drop> !TrivialDrop for D {}
3 Likes

Wow I did not know one can use Drop as a normal trait bound. This could work, maybe?

Weā€™d then also have things like

unsafe impl<'a, T> TrivialDrop for &'a T {}
unsafe impl<'a, T> TrivialDrop for &'a mut T {}
unsafe impl<T> TrivialDrop for *const T {}
unsafe impl<T> TrivialDrop for *mut T {}
unsafe impl<T> TrivialDrop for ManuallyDrop<T> {}
4 Likes

PR for lang-item ManuallyDrop up at https://github.com/rust-lang/rust/pull/52711.

5 Likes

So we can still have generic unions, e.g.

union Foo<T: TrivialDrop> { it: T }
union Bar<T> { it: ManuallyDrop<T> }

?

Iā€™m a bit concerned about backward compatibility, e.g. if before I had:

union Baz { it: Thing }

where Thing has drop code, and now I have:

union Baz { it: ManuallyDrop<Thing> }

then previously to run the drop code I wrote:

  let x = baz.it;
  // a hidden use of drop(x);

and now that code still compiles, but has different semantics, since the drop code doesnā€™t run. I need to update the code to be:

  let ManuallyDrop(x) = baz.it;
  // a hidden use of drop(x);

Yes.

No code will silently change behavior. But yes, people making use of nightly features will have to adapt their code or it may stop compiling. The fact that the type becomes ManuallyDroop should be a clear signal.

I do not see a way to avoid that. This kind of experimentation is what we have nightly features for. Stabilizing this feature would be very ill-advised IMHO -- and we shouldn't keep features that we do not intend to stabilize (except for rustc implementation details, of course).

Ah, moving out of a union. That's another interesting question, when that should be allowed -- tying in with the unresolved question about tracking initialization.

1 Like

I like this proposal a lot. Having a simple mental model of how unions interact with destructors (or donā€™t) is great.

Regarding a ā€œno drop glueā€ auto trait, it sounds like weā€™d want it to always be accurate. std::mem::needs_drop has been mentioned as its run-time equivalent, but it is currently documented as a ā€œhintā€ that is allowed to be conservative and have false positive, all the way to potentially being implemented as fn needs_drop::<T>() -> bool { true }.

I opened an RFC. Unfortunately this got somewhat more complicated than I thought as I had to describe initialization tracking for unions (what was still an open question in this thread), and also discovered that my proposal does not avoid all pitfalls around drop on assignment, but see for yourself.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.