Rust Ownership, Moves, Copy and Clone¶
Rust ownership determines which binding can use a value and when owned resources are released. This lesson uses a small text label to distinguish moving ownership, copying a value and explicitly cloning it; no C++ background is needed.
Prerequisites and outcome¶
Read types and expressions. By the end, predict which variable is usable after an assignment or function call. The next lesson introduces borrowing so a function can inspect text without taking ownership.
An owned String is not a string literal¶
String::from("Pi 4B") creates owned, growable text. The literal "Pi 4B" has type &str, a reference to string data. We use String here to study ownership; UTF-8 and text APIs receive fuller treatment later.
A value has an owner; assigning a non-Copy value to another binding transfers it. The old binding cannot then be used as though it still owned that value. This is a rule about valid use, not a promise that a particular number of machine instructions or physical memory copies occurs.
Moving this String does not request a duplicate of its text allocation. Do not use the shortcut “stack values copy, heap values move”: the type's Copy implementation determines copying, not a value's storage location.
Run a complete ownership example¶
Replace src/main.rs with:
Expected program output:
Rust moves versus Copy and Clone¶
i32 implements Copy, so assigning reading leaves it usable. Copy is a type property, not a runtime choice made according to how large a value looks. Many simple types are Copy; String is not.
| Operation | What happens | Old binding usable? |
|---|---|---|
let copied = reading |
Copy an i32 | Yes |
let moved = original |
Move the String | No |
let duplicate = original.clone() |
Create independently owned String text | Yes |
decorate(moved) |
Transfer the String to the function parameter | No |
The clone occurs before the move. Reversing those statements attempts to clone after ownership transferred. The Copy API documentation also explains why types implementing Drop cannot implement Copy.
clone() explicitly requests duplication according to a type's Clone implementation. For String, the duplicate owns independent text storage. That does not mean every Clone implementation performs a deep copy: later, cloning an Rc shares ownership instead. Copy types also implement Clone.
Passing moved to decorate transfers ownership into its parameter. Returning the resulting String transfers ownership to the caller. The caller cannot reuse moved afterward. Prefer ownership transfer when the receiving function really needs to own a value; use borrowing for inspection rather than cloning just to silence an error.
format! builds a new String; it does not return the input allocation unchanged. The input is dropped when the function finishes, while the result belongs to decorated. The earlier println! borrows its arguments for formatting, so printing moved does not prevent the later ownership transfer.
Scope and resource release¶
An initialized owned value is normally dropped when its owner leaves scope. If it was moved, the old binding does not drop it again. Assigning a new value to an initialized owning variable also drops the old value; explicit drop(value) consumes a value early.
A name can remain in lexical scope after a move yet no longer be usable. A name declared in an inner block is inaccessible outside it even without a move. Reinitialising a moved mutable binding makes it own a new value; it does not restore the old value.
String's cleanup releases its owned allocation. Later we study Drop, resource wrappers and exception boundaries in detail. Do not generalise normal scope exit to process termination: aborts and intentional leaks do not provide the same cleanup guarantees.
The official ownership chapter explains the introductory model; destructors provide precise scope and cleanup rules.
Deliberately failing example¶
Use a separate scratch project for this complete program:
Run cargo check. Expect a moved-value error (E0382). Making original mutable does not restore ownership. Either use the new owner, clone when independent ownership is required, or redesign the operation to borrow, as the next lesson demonstrates.
Exercises and limits¶
- Call
decorate(duplicate)twice: expect the second use to fail. Explain which call consumed the value. - Put a temporary String inside a nested block. Use it inside the block, then try outside: expect a scope error, a different reason from use after move.
- Replace
let moved = originalwithlet moved = original.clone(): both bindings become usable. Explain why that changes the ownership relationship and may add allocation work.
The runtime tests check returned text and independent String cloning. The failing example checks compiler enforcement. Neither measures allocation cost or performance, and neither is a complete model of C++ move semantics; that comparison belongs in the advanced series.
Verification and next step¶
On October 10, 2026, this lesson was verified on a Raspberry Pi 4B with 64-bit user space, kernel 6.18.50+rpt-rpi-v8, Rust and Cargo 1.99.0, and edition 2024. Check, all 3 tests, formatting and debug/release output comparisons passed. The moved-value example and repeated-call and scope exercises failed for the expected reasons. The clone variant also compiled and ran. No hardware or allocation-cost benchmark is claimed.
Continue with references, borrowing and slices.