Rust / the practical course

01 / Foundations

Ownership, places, and moves

Learn what moves, what gets copied, and when a borrow is enough.

55 min + practiceRust 1.98.1 · Edition 2024

By Robert DeVore · Download Markdown

A value and the place holding it

A place is a location a program can refer to: a local variable, a field, an indexed element, or a dereferenced pointer. A value is what the location currently holds. Moving a value transfers it out of a place. The heap allocation it owns may stay at the same address.

String owns and tracks a UTF-8 buffer. If two independent strings both tried to free that buffer, the program could free the same memory twice. Moving a String makes the source unavailable until reinitialized. The new owner normally drops the value when its drop scope ends.

Run the example

cargo run --locked --example 03_ownership
fn label(text: &str) -> usize {
    text.len()
}
fn main() {
    let original = String::from("WARN retry");
    let bytes = label(&original);
    let stored = original;
    assert_eq!(bytes, 10);
    assert_eq!(stored, "WARN retry");
}

View the tested source

label(&original) lends access, so it does not take ownership of the string. let stored = original does transfer the value. No second character buffer is needed. The compiler may optimize away the move itself. A move in Rust source does not promise a particular machine instruction.

Copy is a type property

Some types implement Copy: using their values in a move-like context implicitly copies them and leaves the source usable. Integers are familiar examples. Heap versus stack is not the rule: references can be copied, and a stack-resident struct can own a non-Copy resource. A type implementing Drop cannot also implement Copy.

Clone requests duplication explicitly, but its meaning depends on the type. Cloning a String duplicates its text; cloning an Rc adds an owner to shared data. Before adding .clone() to fix an error, ask what the caller needs: ownership, a temporary borrow, or a separate copy it can change.

Follow destruction, not just allocation

Values are normally dropped when their drop scope ends; moving transfers that responsibility. Fields can sometimes be moved separately, leaving a partially moved struct whose remaining fields are still usable. A type with a destructor restricts such moves because its destructor expects an intact value. Leaking memory with safe operations is possible: Rust does not promise that every destructor always runs. Never base an unsafe abstraction's soundness solely on a caller eventually calling drop.

The early model “one owner” describes ordinary owning values, not a ban on shared ownership. Rc and Arc coordinate multiple owning handles through a reference count. They do not automatically make the contained data mutable or thread-safe.

Compiler drill

Intentionally fails on Rust 1.98.1. Run rustc --edition=2024 drills/moved.rs from the repository.

fn main() {
    let original = String::from("record");
    let stored = original;
    println!("{original} {stored}");
}
Actual compiler diagnostic · Rust 1.98.1
error[E0382]: borrow of moved value: `original`
 --> drills/moved.rs:4:16
  |
2 |     let original = String::from("record");
  |         -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait
3 |     let stored = original;
  |                  -------- value moved here
4 |     println!("{original} {stored}");
  |                ^^^^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let stored = original.clone();
  |                          ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0382`.

The diagnostic identifies the earlier move and the later use. Repair the design before considering duplication. Can the recipient borrow? Can the original caller stop using the value? Are two independently editable strings actually required?

Exercise

Repair the drill without cloning. Then make a second version in which two independent strings are deliberately required and justify the clone in one sentence. In each version, identify which binding is responsible for dropping which allocation.

Solution and acceptance check

For shared read-only access, use let stored = &original; printing both is then valid. Alternatively print only the new owner after a move. For two independently editable strings, use let stored = original.clone() and mutate one to show the other remains unchanged. The two allocations each have an owner and are each dropped once in ordinary execution.

Sources: places and moves, destructors, and Copy.

Find a lesson