Understanding Move Semantics, Copy, and Clone in Rust
In Rust, when you assign a value to a new variable or pass it to a function, the behavior depends on whether the type implements the Copy
trait. Let's explore this with some examples.
Move Semantics
By default, if a type doesn't implement the Copy
trait, assigning it to a new variable or passing it to a function will move the value instead of copying it.
Here's an example with String
, which doesn't implement Copy
:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2
println!("{}", s1); // This line would cause a compile-time error
}
This code won't compile. You'll get an error like this:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:20
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1; // s1 is moved to s2
| -- value moved here
4 |
5 | println!("{}", s1); // This line would cause a compile-time error
| ^^ value borrowed here after move
The ownership of the string data has moved from s1
to s2
, and s1
is no longer valid to use.
The Copy Trait
Types that implement the Copy
trait behave differently. When you assign a Copy
type, it's copied instead of moved. Most primitive types (like integers, floats, and booleans) implement Copy
.
Here's an example:
fn main() {
let x = 5;
let y = x; // x is copied to y
println!("x = {}, y = {}", x, y); // This is fine, x is still valid
}
This code works because i32
(the type of x
) implements Copy
. Both x
and y
are usable after the assignment.
The Clone Trait
For types that don't implement Copy
(like String
), Rust provides the Clone
trait. Clone
allows you to explicitly create a deep copy of a value.
Here's how you can use Clone
with String
:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // Explicitly clone s1
println!("s1 = {}, s2 = {}", s1, s2); // This is fine, s1 is still valid
}
In this case, s2
gets a new allocation with a copy of the data from s1
. Both s1
and s2
are valid and independent.
Implementing Copy and Clone
You can implement Copy
and Clone
for your own types. Here's an example:
#[derive(Debug, Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 5, y: 10 };
let p2 = p1; // p1 is copied to p2 because Point implements Copy
println!("p1 = {:?}", p1); // This is fine, p1 is still valid
println!("p2 = {:?}", p2);
}
In this example, Point
implements both Copy
and Clone
. When we assign p1
to p2
, it's copied, not moved.
Key Points to Remember
- By default, assigning non-
Copy
types moves the value. - Types that implement
Copy
are automatically copied when assigned or passed to functions. Clone
allows explicit copying of values, even for types that don't implementCopy
.Copy
is implicit and cheap, whileClone
is explicit and may be more expensive.- Not all types can implement
Copy
. For example, types that manage resources (likeString
orVec
) typically don't implementCopy
.
Understanding these concepts is crucial for writing efficient Rust code and avoiding unexpected behavior related to ownership and borrowing.