6.0 What This Teaches
- The three ownership rules
- Move semantics - what happens when you assign or pass a value
Clonefor an explicit deep copyCopytypes that duplicate automatically- Shared references (
&T) for read-only borrowing - Mutable references (
&mut T) for exclusive borrowing - How scope controls when memory is freed
6.1 The Three Ownership Rules
- Every value has exactly one owner.
- When the owner goes out of scope, the value is dropped (memory freed).
- There can only be one owner at a time.
6.2 Move Semantics
let a = String::from("hello");
let b = a; // ownership moves from a to b
// println!("{a}"); // compile error: value moved
println!("{b}"); // fine
String allocates on the heap, so assigning it moves rather than copies.
The compiler rejects any attempt to use a after the move - there is no
way to access freed memory.
fn take_ownership(s: String) {
println!("taken: {s}");
} // s is dropped here
let u = String::from("goodbye");
take_ownership(u);
// println!("{u}"); // compile error: u was moved into the function
6.3 Clone - Explicit Deep Copy
.clone(). It performs a full deep copy and both bindings remain valid:
let c = String::from("world");
let d = c.clone();
println!("c = {c}, d = {d}"); // both usable
.clone() is intentionally explicit. Rust does not silently copy expensive
heap data - you opt in so the cost is visible in the code.
6.4 Copy Types
Copy trait. Assigning
or passing them duplicates the bits automatically - no move, no clone required.
Copy types: all integer types, f32, f64,
bool, char, and tuples or arrays made up of Copy
types.
let x = 42;
let y = x; // x is copied, not moved
println!("x = {x}, y = {y}"); // both valid
String is not Copy because it owns heap memory. Copying it
silently would mean two owners for the same allocation - a double-free waiting to happen.
6.5 Shared References - Borrowing Read-Only
&
operator creates a shared reference. Rust guarantees the referenced value will not
be modified while the reference exists.
fn print_len(s: &String) {
println!("length = {}", s.len());
}
let s = String::from("Rust");
print_len(&s); // lend s to the function
println!("still have s: {s}"); // s is still ours
print_len borrows s for its duration. When the function
returns, the borrow ends and full ownership returns to the caller. No memory is freed -
the caller still owns s.
let r1 = &s;
let r2 = &s;
println!("r1 = {r1}, r2 = {r2}"); // fine - both just read
6.6 Mutable References - Borrowing for Mutation
&mut T) lets a borrower modify the value. The
rule is strict: while a mutable reference exists, no other references - shared or
mutable - may exist for the same value. This prevents data races at compile time.
fn append_bang(s: &mut String) {
s.push('!');
}
let mut t = String::from("hello");
append_bang(&mut t);
println!("{t}"); // hello!
mut before you can take a
&mut reference to it.
- At most one
&mutreference at a time. - No
&references while a&mutreference is active.
let mut s = String::from("hi");
let r1 = &s;
let r2 = &mut s; // compile error: cannot borrow as mutable because it is also borrowed as immutable
6.7 Scope Controls Lifetime
}
of the block.
{
let scoped = String::from("temporary");
println!("{scoped}");
} // scoped is dropped here; heap memory freed immediately
println!("scoped is gone");
6.8 Example - All Together
// Ownership - demonstrates move semantics, cloning, Copy types, and borrowing in Rust.
fn print_len(s: &String) {
println!("length = {}", s.len());
}
fn append_bang(s: &mut String) {
s.push('!');
}
fn take_ownership(s: String) {
println!("taken: {s}");
}
fn main() {
// move
let a = String::from("hello");
let b = a;
println!("{b}");
// clone
let c = String::from("world");
let d = c.clone();
println!("c = {c}, d = {d}");
// Copy type
let x = 42;
let y = x;
println!("x = {x}, y = {y}");
// shared borrow
let s = String::from("Rust");
print_len(&s);
println!("still have s: {s}");
// mutable borrow
let mut t = String::from("hello");
append_bang(&mut t);
println!("{t}");
// move into function
let u = String::from("goodbye");
take_ownership(u);
// scope drop
{
let scoped = String::from("temporary");
println!("{scoped}");
}
println!("scoped is gone");
}
hello
c = world, d = world
x = 42, y = 42
length = 4
still have s: Rust
hello!
taken: goodbye
temporary
scoped is gone
6.9 Exercise
Exercise
- Write a function
first_word(s: &String) -> &strthat returns a reference to the first space-delimited word ins. Call it and print the result without movings. Confirm you can still usesafter the call. - Write a function
double_all(v: &mut Vec<i32>)that multiplies every element in the vector by 2. Call it frommainusing a mutable reference. Print the vector before and after. - Declare two
Stringbindings. Move one into a function that prints it, then confirm the compiler rejects a use of that binding after the call. Then repeat using.clone()so both uses succeed.
6.10 Common Mistakes
Using a value after moving it
let a = String::from("hi");
let b = a;
println!("{a}"); // error: value borrowed here after move
a is gone. Use a.clone() if you need both.Taking a mutable and shared reference at the same time
let mut s = String::from("hi");
let r = &s;
s.push('!'); // error: cannot borrow `s` as mutable because it is also borrowed as immutable
println!("{r}");
r is a shared reference still in use when push tries to
mutate s. Drop r before mutating, or restructure so the
borrow ends first.
Forgetting mut on the binding before taking &mut
let s = String::from("hi");
let r = &mut s; // error: cannot borrow `s` as mutable, as it is not declared as mutable
let mut s to allow mutable borrowing.Expecting Clone behavior from assignment
let b = a to
leave a usable, you need let b = a.clone(). Assignment
moves unless the type is Copy.
6.11 Key Terms
| Term | Meaning |
|---|---|
| owner | The single binding responsible for a value; frees it when it goes out of scope |
| move | Transfer of ownership from one binding to another; original is invalidated |
| drop | Automatic freeing of a value when its owner goes out of scope |
| Clone | Trait enabling explicit deep copy via .clone() |
| Copy | Trait marking types that duplicate automatically on assignment (stack-only) |
| borrow | Temporary access to a value via a reference, without taking ownership |
| &T | Shared reference; allows reading; many can coexist |
| &mut T | Mutable reference; allows reading and writing; exclusive - no other references allowed |
| borrow checker | The compiler component that enforces ownership and reference rules |
| lifetime | The span of code during which a reference is valid; enforced at compile time |