Can we fix `Drop` to allow specialization?

Right now, Drop can't be specialized. By specialized I mean the following:

struct Struct<T>(std::marker::PhantomData<T>);

// rustc will complain with:
// error: `Drop` impl requires `T: std::marker::Copy` but the struct it is implemented for does not
// note: the implementor must specify the same requirement
impl<T> Drop for Struct<T> where T: Copy {
    fn drop(&mut self) {}
}

Looking into the source of this, it is implemented in dropck.rs. Looking at the blame, this was implemented due to issue #8142 ("Prohibit specialized drops"). TL;DR: pre-1.0 Rust had bad monomorphized drop glue. That is, types that didn't match the where bounds would still get the Drop implementation applied to them.

Forking this off of the Idea: Make std::mem::needs_drop smarter discussion, I think it would be nice to allow Drop to be implemented for specialized types. This would naturally allow std::mem::needs_drop to return false for the types that don't have Drop implemented for them.

I have a few questions:

  • Is Drop glue monomorphization still broken? Has the issue been fixed in the last 5 years?
    • If yes, is there anything else blocking Drop specialization?
    • If no, how hard would it be to fix it?
7 Likes

I don't have a full understanding of this, but I think there's some issues surrounding lifetimes – just like with the full specialization feature, allowing you to implement Drop with a lifetime bound means that the behavior of the resulting program could depend on the lifetimes, contrary to the current situation where lifetimes essentially disappear at runtime (they cause some code to not compile at all, but have no effect on code that does compile). I think there were some soundness issues related to that.

I don't think there would be lifetime problems around a drop implementation containing the equivalent of C++'s

if constexpr (std::is_same_v<...>) {
  // ...
} else {
  // ...
}

So long as those bounds do not involve lifetimes, I'm not sure that this is actually a problem.

Also, I'm still not clear on why lifetimes are a problem? All types are known at compile-time, except for those of trait objects, which just virtual destructors. There doesn't seem to be a conceptual barrier, a priori, on specialized dtors.

They're erased, so at monomorphization time something might not know whether the lifetime is one that ends up needing drop.

So I think this would be a problem, for example:

struct Foo<'a>(&'a i32);

impl Drop for Foo<'static> {
    fn drop(&mut self) { println!("uhoh"); }
}

fn bar<'a>(_x: Foo<'a>) {
    // _x is dropped here; does it need to run code?
}
1 Like

Could lifetimes be dealt with using the same "always applicable" rule that specialization requires? It seems like that would solve the issue around lifetimes, which I think is the only fundamental issue with specialized Drop impls.

4 Likes

Ooh, I wasn't aware of that work! Yeah, that should presumably do it.

1 Like

Ah yes, right, I forgot you could specialize on something like 'static. I think the "always applicable" rule described above deals with that handily.