Copying values, pt. 2
Let's consider the same example as before, but with a slight twist: using u32
rather than String
as a type.
#![allow(unused)] fn main() { fn consumer(s: u32) { /* */ } fn example() { let s: u32 = 5; consumer(s); let t = s + 1; } }
It'll compile without errors! What's going on here? What's the difference between String
and u32
that makes the latter work without .clone()
?
Copy
Copy
is another trait defined in Rust's standard library:
#![allow(unused)] fn main() { pub trait Copy: Clone { } }
It is a marker trait, just like Sized
.
If a type implements Copy
, there's no need to call .clone()
to create a new instance of the type:
Rust does it implicitly for you.
u32
is an example of a type that implements Copy
, which is why the example above compiles without errors:
when consumer(s)
is called, Rust creates a new u32
instance by performing a bitwise copy of s
,
and then passes that new instance to consumer
. It all happens behind the scenes, without you having to do anything.
What can be Copy
?
Copy
is not equivalent to "automatic cloning", although it implies it.
Types must meet a few requirements in order to be allowed to implement Copy
.
First of all, it must implement Clone
, since Copy
is a subtrait of Clone
.
This makes sense: if Rust can create a new instance of a type implicitly, it should
also be able to create a new instance explicitly by calling .clone()
.
That's not all, though. A few more conditions must be met:
- The type doesn't manage any additional resources (e.g. heap memory, file handles, etc.) beyond the
std::mem::size_of
bytes that it occupies in memory. - The type is not a mutable reference (
&mut T
).
If both conditions are met, then Rust can safely create a new instance of the type by performing a bitwise copy
of the original instance—this is often referred to as a memcpy
operation, after the C standard library function
that performs the bitwise copy.
Case study 1: String
String
is a type that doesn't implement Copy
.
Why? Because it manages an additional resource: the heap-allocated memory buffer that stores the string's data.
Let's imagine that Rust allowed String
to implement Copy
.
Then, when a new String
instance is created by performing a bitwise copy of the original instance, both the original
and the new instance would point to the same memory buffer:
s copied_s
+---------+--------+----------+ +---------+--------+----------+
| pointer | length | capacity | | pointer | length | capacity |
| | | 5 | 5 | | | | 5 | 5 |
+--|------+--------+----------+ +--|------+--------+----------+
| |
| |
v |
+---+---+---+---+---+ |
| H | e | l | l | o | |
+---+---+---+---+---+ |
^ |
| |
+------------------------------------+
This is bad!
Both String
instances would try to free the memory buffer when they go out of scope,
leading to a double-free error.
You could also create two distinct &mut String
references that point to the same memory buffer,
violating Rust's borrowing rules.
Case study 2: u32
u32
implements Copy
. All integer types do, in fact.
An integer is "just" the bytes that represent the number in memory. There's nothing more!
If you copy those bytes, you get another perfectly valid integer instance.
Nothing bad can happen, so Rust allows it.
Case study 3: &mut u32
When we introduced ownership and mutable borrows, we stated one rule quite clearly: there
can only ever be one mutable borrow of a value at any given time.
That's why &mut u32
doesn't implement Copy
, even though u32
does.
If &mut u32
implemented Copy
, you could create multiple mutable references to
the same value and modify it in multiple places at the same time.
That'd be a violation of Rust's borrowing rules!
It follows that &mut T
never implements Copy
, no matter what T
is.
Implementing Copy
In most cases, you don't need to manually implement Copy
.
You can just derive it, like this:
#![allow(unused)] fn main() { #[derive(Copy, Clone)] struct MyStruct { field: u32, } }
Exercise
The exercise for this section is located in 04_traits/12_copy