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(),
}
}
}
Why do I need to specify an empty Copy
trait implementation?
Is there a simpler way to do this or do I have to re-think something?
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With