Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the Copy trait needed for default (struct valued) array initialization?

When I define a struct like this, I can pass it to a function by value without adding anything specific:

#[derive(Debug)]
struct MyType {
    member: u16,
}

fn my_function(param: MyType) {
    println!("param.member: {}", param.member);
}

When I want to create an array of MyType instances with a default value

fn main() {
    let array = [MyType { member: 1234 }; 100];
    println!("array[42].member: ", array[42].member);
}

The Rust compiler tells me:

error[E0277]: the trait bound `MyType: std::marker::Copy` is not satisfied
  --> src/main.rs:11:17
   |
11 |     let array = [MyType { member: 1234 }; 100];
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `MyType`
   |
   = note: the `Copy` trait is required because the repeated element will be copied

When I implement Copy and Clone, everything works:

impl Copy for MyType {}
impl Clone for MyType {
    fn clone(&self) -> Self {
        MyType {
            member: self.member.clone(),
        }
    }
}
  1. Why do I need to specify an empty Copy trait implementation?

  2. Is there a simpler way to do this or do I have to re-think something?

  3. Why does it work when passing an instance of MyType to the function by value? My guess is that it is being moved, so there is no copy in the first place.

like image 962
Daniel Avatar asked Jan 10 '15 13:01

Daniel


2 Answers

Contrary to C/C++, Rust has very explicit distinction between types which are copied and which are moved. Note that this is only a semantic distinction; on the implementation level move is a shallow bytewise copy, however, the compiler places certain restrictions on what you can do with variables you moved from.

By default every type is only moveable (non-copyable). It means that values of such types are moved around:

let x = SomeNonCopyableType::new();
let y = x;
x.do_something();      // error!
do_something_else(x);  // error!

You see, the value which was stored in x has been moved to y, and so you can't do anything with x.

Move semantics is a very important part of ownership concept in Rust. You can read more on it in the official guide.

Some types, however, are simple enough so their bytewise copy is also their semantic copy: if you copy a value byte-by-byte, you will get a new completely independent value. For example, primitive numbers are such types. Such property is designated by Copy trait in Rust, i.e. if a type implements Copy, then values of this type are implicitly copyable. Copy does not contain methods; it exists solely to mark that implementing types have certain property and so it is usually called a marker trait (as well as few other traits which do similar things).

However, it does not work for all types. For example, structures like dynamically allocated vectors cannot be automatically copyable: if they were, the address of the allocation contained in them would be byte-copied too, and then the destructor of such vector will be run twice over the same allocation, causing this pointer to be freed twice, which is a memory error.

So by default custom types in Rust are not copyable. But you can opt-in for it using #[derive(Copy, Clone)] (or, as you noticed, using direct impl; they are equivalent, but derive usually reads better):

#[derive(Copy, Clone)]
struct MyType {
    member: u16
}

(deriving Clone is necessary because Copy inherits Clone, so everything which is Copy must also be Clone)

If your type can be automatically copyable in principle, that is, it doesn't have an associated destructor and all of its members are Copy, then with derive your type will also be Copy.

You can use Copy types in array initializer precisely because the array will be initialized with bytewise copies of the value used in this initializer, so your type has to implement Copy to designate that it indeed can be automatically copied.

The above was the answer to 1 and 2. As for 3, yes, you are absolutely correct. It does work precisely because the value is moved into the function. If you tried to use a variable of MyType type after you passed it into the function, you would quickly notice an error about using a moved value.

like image 124
Vladimir Matveev Avatar answered Nov 17 '22 10:11

Vladimir Matveev


Why do I need to specify an empty Copy trait implementation?

Copy is a special built-in trait such that T implementing Copy represents that it is safe to duplicate a value of type T with a shallow byte copy.

This simple definition mean that one just needs to tell the compiler those semantics are correct, since there's no fundamental change in run-time behaviour: both a move (a non-Copy type) and a "copy" are shallow byte copies, it's just a question of if the source is usable later. See an older answer for more details.

(The compiler will complain if the contents of MyType isn't Copy itself; previously it would be automatically implemented, but that all changed with opt-in built-in traits.)

Creating an array is duplicating the value via shallow copies, and this is guaranteed to be safe if T is Copy. It is safe in more general situations, #5244 covers some of them, but at the core, a non-Copy struct won't be able to be used to create a fixed-length array automatically because the compiler can't tell that the duplication is safe/correct.

Is there a simpler way to do this or do I have to re-think something (I'm coming from C)?

#[derive(Copy)]
struct MyType {
    member: u16
}

will insert the appropriate empty implementation (#[derive] works with several other traits, e.g. one often sees #[derive(Copy, Clone, PartialEq, Eq)].)

Why does it work when passing an instance of MyType to the function by value? My guess is that it is being moved, so there is no copy in the first place.

Well, without calling the function one doesn't see the move vs. copy behaviour (if you were to call it twice the same non-Copy value, the compiler would emit an error about moved values). But, a "move" and a "copy" are essentially the same on the machine. All by-value uses of a value are shallow copies semantically in Rust, just like in C.

like image 22
huon Avatar answered Nov 17 '22 09:11

huon