Must implement Copy for elements to derive Clone for arrays?


#1

Given this:

#[derive(Debug, Clone)]
struct A { vecs: [Vec<u8>; 10] }

rustc complains:

<anon>:2:12: 2:31 error: the trait `core::marker::Copy` is not implemented for the type `collections::vec::Vec<u8>` [E0277]
<anon>:2 struct A { vecs: [Vec<u8>; 10] }
                    ^~~~~~~~~~~~~~~~~~~
<anon>:1:17: 1:22 note: in this expansion of #[derive_Clone] (defined in <anon>)
<anon>:2:12: 2:31 help: see the detailed explanation for E0277
<anon>:2:12: 2:31 note: required by `core::clone::Clone::clone`

Why does derived Clone for arrays require Copy on the array elements? Wouldn’t Clone be enough?

https://play.rust-lang.org/?gist=c7f2203deffa80d00f81&version=stable


#2

It is difficult to implement Clone safely for arrays because you have to deal with the compiler attempting to drop partially uninitialized memory in case the .clone() method on the element type panicks. (This is generally bad behavior but you have to make sure that you don’t violate memory safety if it does happen.) We don’t quite have the primitives to make this implementation ergonomic yet. It’d be nice to have a wrapper type which tells the compiler to not generate Drop glue for the contained type, and to have integer generics so we can implement it for all arrays instead of the current kludge of using macros to duplicate the code for arrays of size 0 - 32.

Arrays with Copy elements are intrinsically Copy themselves, so the Clone implementation can trivially dereference and return self by-value.


#3

Doesn’t that apply to having a panic in the middle any kind of initialization?

Heck yes.


#4

The other current cases of initialization all have very clearly defined semantics. For example:

struct Foo {
    val: i32,
}

fn main() {
    Foo {
        val: panic!(),
    }
}

This might seem like it would cause a similar issue, but the struct simply isn’t considered “complete” until all the fields are initialized; the values of any expressions in the initializer before panic!() would be dropped, but not Foo itself.

Vec, Box, Rc, etc., all use heap allocations through raw pointers, which the compiler sees as completely opaque. Each type has to implement Drop and correctly free its contained data; the compiler won’t touch it.

For arrays, you have to start with uninitialized stack memory, which is most likely garbage. See, the stack doesn’t get zeroed between invocations of a function–that would be way too slow. And since you’re working with a bare array, if you panic!() the compiler will try to Drop the whole thing. In your case with Vec<u8>, it will interpret the garbage bytes in the array as pointers, capacities, and lengths for vectors that never existed, and try to free bogus allocations:

use std::mem;

let array: [Vec<u8>; 16] = unsafe { mem::uninitialized() };
panic!();
// Next stop, undefined behavior! 
// (For sanely implemented systems, this will probably just segfault or hit an assertion in the allocator)

Unfortunately, there is no wrapper type in the stdlib that tells the compiler to not generate Drop glue for its contents, not even UnsafeCell (which surprised me). This is explained in the Destructors chapter of the Rustonomicon.

However, we do have std::panic::recover() now which we can use to cover this edge case, but I would consider it quite a heavyweight solution–there’s a lot of code behind it, and we don’t necessarily want to stop the panic, we just want to have control over what gets dropped. Thus, a wrapper type that elides the destructor so we can implement our own with full control would be really useful. There’s been quite a bit of discussion about this on this RFC and one or two others that I’m having trouble finding. Edit: found the one I was thinking of.

In the meantime, you might want to take a look at the arrayvec crate, which seems to cover your use-case. It uses an interesting hack to avoid calling destructors on uninitialized memory, but the drop glue is still generated so it’s not entirely optimal.