Skip to content
Select theme

How to tell whether a type will move or copy

Short answer: ask the compiler. Write fn is_copy<T: Copy>() {} once, call it with the type you’re wondering about, and read the answer: an error means assignment moves the value, and no error means it copies. Your own structs are never Copy until you ask for it, even when every field is.

Copy is a marker trait with no methods. It says a value can be duplicated by copying its bytes, and it does nothing on its own — which makes it a good question to ask. A function bounded on Copy accepts a type only if that type is Copy:

none of those four lines was an error

The only line of output is the println!. The answers are the silent absence of errors, and that’s the awkward part of testing this in Rust: there’s no function that returns a bool, because Copy is a property of the type at compile time. Either the program compiles or the line you added doesn’t. The test takes any type name, including ones you didn’t write, and generics — Option<i32> is Copy because the thing inside it is.

Swap in a type that isn’t Copy and the compiler names it:

error[E0277]: the trait bound `String: Copy` is not satisfied
--> src/main.rs:4:15
|
4 | is_copy::<String>();
| ^^^^^^ the trait `Copy` is not implemented for `String`
|
note: required by a bound in `is_copy`
--> src/main.rs:1:15
|
1 | fn is_copy<T: Copy>() {}
| ^^^^ required by this bound in `is_copy`
For more information about this error, try `rustc --explain E0277`.

The header names the trait and the type, and the caret sits under the type argument you wrote. The note: points back at is_copy, because that’s where the requirement came from. For a library type you don’t own, the header is the whole answer.

The part that catches people is that a struct built entirely from Copy fields is still not Copy:

error[E0277]: the trait bound `Point: Copy` is not satisfied
--> src/main.rs:9:15
|
9 | is_copy::<Point>();
| ^^^^^ the trait `Copy` is not implemented for `Point`
|
note: required by a bound in `is_copy`
--> src/main.rs:1:15
|
1 | fn is_copy<T: Copy>() {}
| ^^^^ required by this bound in `is_copy`
help: consider annotating `Point` with `#[derive(Copy)]`
|
3 + #[derive(Copy)]
4 | struct Point {
|
For more information about this error, try `rustc --explain E0277`.

Point is two i32s and a name, and Rust still moves it. Deriving is opt-in. The help: at the bottom tells you so, though following it word for word gives you a second error, because a Copy type has to be Clone too:

error[E0277]: the trait bound `Point: Clone` is not satisfied
--> src/main.rs:2:8
|
1 | #[derive(Copy)]
| ---- in this derive macro expansion
2 | struct Point {
| ^^^^^ the trait `Clone` is not implemented for `Point`
|
note: required by a bound in `Copy`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/marker.rs:454:0
help: consider annotating `Point` with `#[derive(Clone)]`
|
2 + #[derive(Clone)]
3 | struct Point {
|
For more information about this error, try `rustc --explain E0277`.

So write both:

a is still usable: 1 2
b copies it: 1 2

That’s everything Copy changes at a call site: let b = a; copies instead of moving, so a is still usable afterwards.

A field that isn’t Copy blocks the whole struct, and the error points at the field:

error[E0204]: the trait `Copy` cannot be implemented for this type
--> src/main.rs:2:8
|
1 | #[derive(Copy, Clone)]
| ---- in this derive macro expansion
2 | struct Label {
| ^^^^^
3 | text: String,
| ------------ this field does not implement `Copy`
For more information about this error, try `rustc --explain E0204`.

A destructor blocks it too, even when every field is Copy:

error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
--> src/main.rs:2:8
|
1 | #[derive(Copy, Clone)]
| ---- in this derive macro expansion
2 | struct Noisy {
| ^^^^^ `Copy` not allowed on types with destructors
|
note: destructor declared here
--> src/main.rs:7:5
|
7 | fn drop(&mut self) {
| ^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0184`.

Drop and Copy never coexist, because a copied value would be cleaned up twice. Either error leaves you with a type that moves, which is fine — most types are supposed to. If you’re not sure which field is at fault, run the test from the top of this page on the field’s type on its own. It’s the same one function, and it names the type that fails.