RustArena

Ownership and moves in Rust

Rust's ownership rules explained: one owner per value, moves on assignment and function calls, drop at end of scope, and how to fix use after move.

Ownership is how Rust manages memory without a garbage collector and without you calling free. The compiler checks three rules:

  1. Every value has exactly one owner, the variable (or struct field, or collection) responsible for it.
  2. There is only one owner at a time.
  3. When the owner goes out of scope, the value is dropped: its memory (and any file, socket or lock it holds) is released.

Rule 3 means cleanup always happens, exactly once, at a place you can see in the code. Rules 1 and 2 are what make "exactly once" possible.

Moves

A String is a small handle on the stack (pointer, length, capacity) pointing at text on the heap. If assigning one String to another simply copied the handle, two variables would point at the same text and both would try to free it: a double free. Rust prevents that by moving instead:

fn main() {
    let s1 = String::from("crab");
    let s2 = s1;
    println!("{s1} {s2}");
}
error[E0382]: borrow of moved value: `s1`

After let s2 = s1;, s2 owns the text and s1 is no longer usable. Nothing on the heap was copied; the move is as cheap as copying three numbers. The compiler simply stops you from using the old name.

The same happens when you pass a value to a function: the parameter becomes the owner, and the value is dropped when the function ends, unless the function gives it back by returning it.

fn shout(mut msg: String) -> String {
    msg.push('!');
    msg
}

fn main() {
    let msg = String::from("hello");
    let msg = shout(msg);
    println!("{msg}");
}
hello!

What moves and what copies

Simple values that live entirely on the stack (i32, f64, bool, char, and tuples or arrays of them) are Copy: assigning them duplicates the bits, and both variables stay usable. Types that own heap memory or other resources (String, Vec<T>, Box<T>, HashMap, files) move.

Dropping

When a value's owner goes out of scope, Rust calls its drop code automatically. Values are dropped in the reverse order they were created. You can run it early with drop(value). This pattern (resource tied to an owner's scope) is called RAII, and it's why Rust code doesn't need finally blocks or manual close() calls.

How to fix "use after move"

Best practices at work

Example

struct Ticket {
    id: u32,
    title: String,
}

impl Drop for Ticket {
    fn drop(&mut self) {
        println!("closing ticket {}", self.id);
    }
}

fn summarize(title: &str) -> usize {
    title.split_whitespace().count()
}

fn archive(t: Ticket) {
    println!("archiving #{}: {}", t.id, t.title);
} // t is dropped here

fn main() {
    let first = Ticket { id: 1, title: String::from("Login page is slow") };
    let second = Ticket { id: 2, title: String::from("Typo in footer") };

    println!("{} words", summarize(&first.title)); // borrowed: first is still ours
    archive(first); // moved: archive owns it now and drops it

    let moved = second; // ownership moves to `moved`
    println!("still open: #{}", moved.id);

    let a = 5;
    let b = a; // i32 is Copy: both stay usable
    println!("{a} {b}");
    println!("end of main");
} // `moved` is dropped here

Output:

4 words
archiving #1: Login page is slow
closing ticket 1
still open: #2
5 5
end of main
closing ticket 2

Practice it