A type that is sometimes Drop and sometimes Copy: is this even theoretically possible?

(This kind of toes the line between irlo and urlo, as well as between the libs and language categories.)

Is it even theoretically possible to have a coherent language extension that allows a type to conditionally implement both Drop and Copy (in a disjoint manner, of course)?

My motivating example is `erasable::Thin|. It's a type roughly formulated as

struct Thin<Ptr: CanBeErased> {
    ptr: ErasedPtr,
    phantom<Ptr>,
}

(ErasedPtr is just ptr::NonNull<VoldemortZST>.) And, ideally, I'd want to support

impl<Ptr: CanBeErased> Copy for Thin<Ptr>
where
    Ptr: Copy,
{}

impl<Ptr: CanBeErased> Drop for Thin<Ptr>
where
    Ptr: !Copy,
{
    fn drop(&mut self) {
        let ptr: Ptr = Ptr::unerase(self.ptr);
        drop(ptr);
    }
}

This obviously isn't possible with current Rust, for multiple reasons:

  • Drop cannot be bounded any differently than on the base structure (Drop impl must be universal).
  • Negative bounds cannot be expressed (!Copy).
  • Drop and Copy are mutually exclusive traits. (At least for a given instantiation of a type. As Drop is universal, we don't know whether a conditional Drop could share space with Copy soundly.)
  • Coherence checker doesn't like having to prove disjointness of impls.

So my actual question here is: is it feasible for some future version of Rust to support this code (or something equivalent)?

I remain unconvinced as of posting this. While this is a "simple" case due to "obvious" disjointness (Trait and !Trait, there are a number of remaining flaws I'm worried about:

  • Drop is special, and very important to soundness and coherence of the Rust safety model. I know Drop impls used to not require being universal (pre-1.0) but were given that requirement to make them easier for the compiler to reason about.
  • I'm pretty sure that we decided we'd ideally like !Trait mean "explicitly promised to not implement Trait", either by explicit impl !Trait or otherwise coherently prohibited from implementing the trait (maybe?). That way implementing a trait you didn't before still can't be a breaking change. (Thus neither impls would cover types which didn't impl Copy nor Drop, but might have (implementation detail) Drop glue.)

So I think it'll always be impossible to express Thin in a way that both inherits Copy and drop glue from its wrapped type in Rust, forever. I'd love to be proven wrong, though, even if I never see it available in rustc.

1 Like

Maybe not directly, but there's always specialization plus associated types…

#![feature(specialization)]
struct Thin<Ptr> { inner: <Ptr as ThinHelper>::Inner }
trait ThinHelper { type Inner; }
impl<Ptr> ThinHelper for Ptr { default type Inner = ThinDrop<Ptr>; }
impl<Ptr: Copy> ThinHelper for Ptr { type Inner = ThinCopy<Ptr>; }
struct ThinDrop<Ptr> { phantom: std::marker::PhantomData<Ptr> }
impl<Ptr> Drop for ThinDrop<Ptr> { fn drop(&mut self) { /* ... */ } }
#[derive(Copy, Clone)]
struct ThinCopy<Ptr: Copy> { phantom: std::marker::PhantomData<Ptr> }
5 Likes