Impossible to have a container that is either Copy or Drop depending on T?

I understand that Copy and Drop are mutually exclusive. However I would like to implement a container that keeps its data on the stack, and has the same Copy/Drop'edness of the type that it is containing. However as far as I can tell that is impossible in current rust. Starting with this:

#![allow(dead_code)]

struct Foo<T> {
    data: [std::mem::MaybeUninit<T>; 4],
}

// impl<T: Copy> Copy for Foo<T> {}

impl<T> Drop for Foo<T> {
    fn drop(&mut self) {
        for element in self.data.iter_mut() {
            unsafe {
                std::ptr::drop_in_place(element.as_mut_ptr());
            }
        }
    }
}

fn main() {}

If you uncomment the line containing the impl of Copy, you will get an error because the container also implements Drop. If you try and make it so that the impl of Drop depends on T: Drop, which in theory would make it so that both impls never apply at the same time, you get this error:

error[E0367]: `Drop` impl requires `T: Drop` but the struct it is implemented for does not
 --> src/main.rs:9:9
  |
9 | impl<T: Drop> Drop for Foo<T> {
  |         ^^^^
  |
note: the implementor must specify the same requirement
 --> src/main.rs:3:1
  |
3 | / struct Foo<T> {
4 | |     data: [std::mem::MaybeUninit<T>; 50],
5 | | }
  | |_^

No rationale for the restriction is given even with --explain. Of course if you follow the advice then you end up making it so data can't be Copy, so Foo then can't be either.

Also whether you manually impl Copy or use #[derive(Copy)] appears to make no difference.

Is there a workaround? Or relevant RFCs/issues I could subscribe to?

Well why should it? The trait has the same signature and requirements either way.

That is incorrect. A type does not need to explicitly implement Drop in order for it to be non-Copy, because Rust's automatic memory managment could not work otherwise. If you have a type like struct Wrapper<T>(Vec<T>), then it will need Drop glue and will not be able to be Copy even though it never ever declares to impl Drop explicitly.


As a workaround, you could just require T: Default and store a [T; 4] instead of [MaybeUninit<T>; 4], or even [Option<T>; 4] in order to get rid of the Default bound. Perhaps add an explicit length: usize field as well, if you need a dynamically-sized collection.

I figured there was the possibility that in the derive case rustc could magically inspect the type to decide whether it Drops (I realize procedural macros in general cannot do this but since derive(Copy) is directly implemented by the compiler I thought it might be an exception). But apparently not.

Sure but I guess in that case we would say that it implicitly implements Drop? So then this would be a way to make sure you don't explicitly implement Drop if it hasn't already been implicitly implemented.

I'm still not sure why the restriction exists in the first place that you can't have different drop implementations based on bounds on T? It is inconsistent with other traits. Also in this case it is used just to decide whether to have Drop at all, which I suspect is not what the error is trying to prevent.

It is even weirder because after upgrading rustc the compiler now recommends that I try using std::mem::needs_drop to do a run time check to see whether or not my loop should run, instead of the cheaper compile time check making my impl conditional on T: Drop would in theory accomplish.

Well needs_drop::<T>() depends on purely its compile-time type argument. An if needs_drop::<T>() call is virtually guaranteed to be optimized (inlined, constant-folded, and subsequently DCE'd) away.

Not necessarily in a debug build though, and the docs for needs_drop implies that's the case it exists to speed up. That's why it's weird -- doing this at the type system level is always faster, so why explicitly prohibit people from doing it there?

It isn't "explicitly prohibiting", it's a limitation of the type system. I'm not sure if it's possible to alleviate this problem, but there exist workarounds (for which you explicitly asked for, hence my answers).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.