Why does my String not implement Copy?
Short answer: Copy says an assignment may duplicate a value by copying its bytes and leave the
original in place. A String’s bytes are a pointer, a length and a capacity, and the text sits in the
buffer they point at. Two copies of those words are two owners of one buffer, and both would free it.
So String has no Copy impl, and let b = a; moves instead.
A move is already a byte copy
Section titled “A move is already a byte copy”Assignment copies bytes either way. When the type is Copy the source stays usable; when it isn’t, the
source is retired. How move semantics work in Rust points
at the line of documentation that describes the copy: ptr::read “creates a bitwise copy of T,
regardless of whether T is Copy”. A move is that copy, plus the compiler ruling the old binding out
of use.
So Copy is a request to skip the retiring and keep both. For an i32 that’s free: four bytes, nothing
shared, two equal numbers. For a String the bytes aren’t the value; they’re directions to it.
What the second copy would point at
Section titled “What the second copy would point at”Rust won’t let you write the next block, which is the point — it makes the copy by hand, the one a Copy
impl would license:
use std::mem::ManuallyDrop;use std::ptr;
fn main() { let owned = String::from("hello"); let words = ManuallyDrop::new(owned);
// The bitwise copy a `Copy` impl would be allowed to make, pointer and all. let duplicate = ManuallyDrop::new(unsafe { ptr::read(&*words) });
println!("words -> {:p}", words.as_ptr()); println!("duplicate -> {:p}", duplicate.as_ptr()); println!("one buffer, two owners: {}", words.as_ptr() == duplicate.as_ptr()); println!("duplicate reads: {}", duplicate.as_str());}words -> 0x1032310b0duplicate -> 0x1032310b0one buffer, two owners: trueduplicate reads: helloThe addresses change on every run; what doesn’t is that they’re equal. Five bytes of text, one
allocation, two Strings that each believe they own it, and neither one owning a copy of the text.
ManuallyDrop is what makes the experiment legal — it switches the destructor off, so nothing frees the
buffer and the program exits, leaking five bytes on the way.
Two owners, one free
Section titled “Two owners, one free”Put two ordinary Strings in that position and both would run the destructor: one allocation, freed
twice. That’s the double free
Why can’t you use a value after moving it? counts,
and it’s the reason String is refused Copy. A Copy type has to be safe to duplicate with a byte
copy, and a String isn’t — the copy would duplicate the pointer, not the buffer. Rust’s answer is to
not let a second owner exist, so let duplicate = owned; moves and one binding keeps the job of freeing.
You can’t add the impl yourself either: String isn’t your type and Copy isn’t your trait, and Rust
only accepts an impl when one of the two is yours.
What to write instead
Section titled “What to write instead”A borrow owns nothing, so copying one is harmless — and &str really is Copy:
fn main() { let owned = String::from("hello");
let borrowed: &str = &owned; let second = borrowed;
println!("borrowed: {borrowed}"); println!("second: {second}"); println!("owned is still here: {owned}");
let cloned = owned.clone(); println!("cloned: {cloned}");}borrowed: hellosecond: helloowned is still here: hellocloned: hellosecond = borrowed would end borrowed if &str moved; it doesn’t, and both lines print. When you
want a second owner rather than a second reader, clone() allocates a buffer of its own — a copy of the
text, not of the pointer.
Related
Section titled “Related”- Why can’t you use a value after moving it? — the double free this page’s rule prevents
- How to tell whether a type will move or copy — the test for any type, including your own
- What happens to the original variable after a move? — the binding a move leaves behind
- Moves and ownership — the rest of this chapter
- Next in this chapter: how to pass ownership of a value to a function