ArrayVec<T> : Copy for T: Copy?

I am looking at:

pub struct ArrayVec<T, const CAP: usize> {
    // the `len` first elements of the array are initialized
    xs: [MaybeUninit<T>; CAP],
    len: LenUint,
}

and I am wondering: is it possible to get ArrayVec<T> : Copy for T: Copy.

MaybeUnit seems to support this:

#[stable(feature = "maybe_uninit", since = "1.36.0")]
// Lang item so we can wrap other types in it. This is useful for coroutines.
#[lang = "maybe_uninit"]
#[derive(Copy)]
#[repr(transparent)]
pub union MaybeUninit<T> {
    uninit: (),
    value: ManuallyDrop<T>,
}

Is there any UB that pops up to have ArrayVec<T> : Copy for T: Copy ?

Basically, it can't because it implements Drop. There can be another type that only stores Copy types, though. It seems like they had issues with UB in #261 but those should be solvable.

2 Likes

Ah, I see, if we abstract a bit, is the core of the problem as follows:

It is currently impossible to define a generic type Foo where

impl <T: Drop> Drop for Foo<T> { ... }
impl <T: Copy> Copy for Foo<T> { ... }

so instead, if we wanted this, we have to define generics Foo_Drop and Foo_Copy<T: Copy> ?

It's not that simple, because even if T doesn't implement Drop, it could still need to be dropped if it contains a type that implements Drop. There is a function for that kind of check, but not a trait, and you can't put new traits on Drop impls anyway.

You need to define specific structs for each, not generics. That's why the PR makes ArrayVecCopy.

1 Like

Hmm, that’s an interesting problem. The first thought of “can we change this” and possibly allow some

impl<T: NeedsDrop> Drop for Foo<T> { … }
impl<T: Copy> Copy for Foo<T> { … }

situation (i.e. a trait version of needs_drop, and a rule that Drop implementations are allowed to be limited by additional …: NeedsDrop constraints) runs into the issue that this isn’t actually the desired behavior in light of more than one parameter. E.g. if I wrote a type

struct Foo<T, S>(/* …logically contains T and S… */);

that needs custom logic to find out where and if T and/or S values are actually contained & owned that need be dropped, the correct implementation isn’t

impl<S: NeedsDrop, T: NeedsDrop> Drop for Foo<S, T> { … }

because that reads as “custom logic is only required of S and T need drop glue”, whereas that you really need here is an “or”, not an “and”.


More precisely marking what Drop does, down to marking what contained types can be dropped and which cannot (though also vastly different in its actual goal) is something that’s also being handled by the may_dangle attributes; see e.g. also RFC PR 3417.


With only the motivation of making fn needs_drop’s reporting more accurate, I had opened an internals thread some time back; this of course is also strongly related, though the way to mark the trait with an associated const NEEDS_DROP: bool in the Drop trait (which makes using the logical “or” we need, as discussed above, as trivial as writing “||”) is probably incompatible with generically checking a Copy implementation for lack of overlap with cases that do end up needing to be dropped, as would be needed here.

While or constraints can’t currently be written in the general case, this one could be:

impl<S, T> Drop for Foo<S, T> where (S, T): NeedsDrop { … }

A slight variant on the NeedsDrop idea is to allow : !Copy bounds for Drop implementations. It’s not quite as precise as NeedsDrop, but that might make things easier on the compiler/soundness side. Especially if !Copy is restricted to appear only for Drop impls— Less chance to accidentally let out the specialization unsoundness gremlins that way.

1 Like

Drop is not allowed to have any constraints that are not on the type itself:

Isn't that because generic code would otherwise have to specialize on the Drop impl, and that would be unsound if it allowed arbitrary constraints? Some restricted kind of bounds might be sound in the future though, they are just not supported right now.

1 Like

I'm not certain, but I think even !Copy bounds would be problematic, because impl Copy can be constrained by lifetimes in all the ways that make feature(specialization) unsound. For example, you could have impl<T: 'static> Copy for Foo<T> {}, and AIUI that lifetime is not sufficiently distinguished from arbitrary 'a throughout the compiler.

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.