Is `[T; N]` special with drop rules?

Hello, this is currently impossible to implement Copy for types that implement Drop, but it is also impossible to impl <T: !Copy> Drop for Foo<T>. Bit it seems that [T; N] actually does achieve it - when T implements Drop, all elements get dropped, while it is still Copy if T is, no Drop impl. Is it special? Or is there some way to create a similar type ourselves?

Aren't those two different cases, and therefore they could easily be two different impls?:

  • When T implements Copy then neither T nor [T; N] implements Drop, and [T; N] implements Copy.
  • When T does not implement Copy then both T and [T; N] implement Drop, and [T; N] does implement Copy.

Oh -- but we don't have negative bounds. Nevermind, now I see that you're asking a more advanced question than I can answer. :slight_smile:

[T; N] is not special in this regard; you can define such type yourself:

#[derive(Clone, Copy)]
pub struct Foo<T>(T);

Then if T: Copy Foo<T> is also Copy, and if T: Drop the same is true for Foo<T> (more precisely, it has a drop glue). This is because there isn't an explicit Drop impl, but rather an implicit compiler-generated one for all fields.

2 Likes

for the E0367 diagnostic, Copy (or rather !Copy) is not special in this case, any trait bounds in the impl Drop that is different from the type definition will be rejected: specialization is simply not supported.

the negative bound is a red herring here: it will itself give a diagnostic, but it's orthogonal to the specialization one.

for example, this will trigger E0367 too:

trait SomeTrait {}
struct SomeType<T>(T);
impl<T: SomeTrait> Drop for SomeType<T> { ... }

no, nothing special here.

first, "when T implements Drop, all elements get dropped ..." -- this does NOT need a Drop implementation for the array type (or any type with generic T), this will always happen (unless your type definition or Drop impl does something special to prevent it, e.g. ManuallyDrop). you can think of the Drop like some kind of user defined "hook" that is called automatically by the compiler before the fields get dropped. the hook is optional, but the fields will always get dropped.

second, "... it is still Copy if T is" -- it is just an explicit impl in the core crate, defined here:

5 Likes

Here's some documentation on destructors. As was mentioned, T: Drop not being met does not mean that T does not have a destructor. For this reason, a T: Drop bound is pretty useless, and generates a warning.

See also needs_drop.

7 Likes

I see... I was thinking about heapless::Vec. But is there a way to have ManuallyDrop and Drop impl that will work like that?

like what? can you be more specific about your question?

Is it possible today to have a struct equivalent to this?

#[derive(Clone)]
pub struct Vec<T, const N: size>(usize, [MaybeUninit<T>; N]);

impl<T: Copy, const N: usize> Copy for Vec<T, N> {};

impl<T: !Copy, const N: usize> Drop for Vec<T, N> {
    fn drop(&mut self) {
        // Drop in place self.1[0..self.0]
    }
}

I hope it is "more specific" enough.

no.

as stated previously, you cannot implement Drop with specialized bounds different than the bounds in the type definition, or you'll get E0367

and because Vec (semantically) is a non-trivial type, the necessity for a Drop implementation means the same Vec type in this case cannot be Copy (EDIT: E0184 and the linked issue tracker indicates it can be made possible in theory, if the compiler can prove the Drop impl is idempotent, but I don't think it is useful in general to be worthy to add the feature to the language).

but why do you want a Vec-like container type to be Copy in the first place? is Clone not applicable for your use case?

anyways, if you must have a Vec-like collection that is Copy, you'll have to make separate types for Copy and non-Copy cases, you cannot have a single Vec type for both cases.

/// can be used with any `Sized` element types
#[derive(Clone)]
struct Vec<T, const N: usize> {
    ...
}
impl<T, const N: usize> Drop for Vec<T, N> {
    ...
}
/// can only be used with `Copy` element types
#[derive(Clone, Copy)]
struct VecCopy<T: Copy, const N: usize> {
    ...
}
2 Likes

I don't believe it's possible any more and that E0184 should be updated accordingly.

Quoting Niko from here:

PhantomData is Copy, which in NLL implies that dropping it has no effect.

And here's an example of borrow checking allowing something due to a Copy bound (there's probably a simpler example).

Edit: Ok, after some mulling, maybe technically possible over an edition where Copy bounds turn into Copy + !Drop, or if Copy bounds imply !Drop but you can add + ?Drop (and the borrow checker relies on !Drop instead).

(But I doubt that will ever happen.)

1 Like