Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
785 views
in Technique[技术] by (71.8m points)

rust - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...