Skip to content
Select theme

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.

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.

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:

words -> 0x1032310b0
duplicate -> 0x1032310b0
one buffer, two owners: true
duplicate reads: hello

The 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.

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.

A borrow owns nothing, so copying one is harmless — and &str really is Copy:

borrowed: hello
second: hello
owned is still here: hello
cloned: hello

second = 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.