Rust Ownership

Ownership is the set of compile-time rules that lets Rust manage memory safely without a garbage collector, and it underlies almost every other language feature.

The Three Rules of Ownership

Rust's ownership model rests on three simple rules enforced entirely at compile time: each value in Rust has a single variable that is its owner; there can only be one owner at a time; and when the owner goes out of scope, the value is dropped automatically. These rules eliminate entire categories of bugs — double frees, use-after-free, and dangling pointers — without needing a runtime garbage collector.

Move Semantics

For heap-allocated types like `String` or `Vec<T>`, assigning a value to a new variable or passing it to a function does not copy the underlying data. Instead, Rust performs a move: ownership transfers to the new binding, and the original variable is invalidated by the compiler. This is different from languages like Python or JavaScript, where assignment either copies or creates a shared reference that both names can still use.

A Move Invalidates the Original Binding

fn main() {
    let original = String::from("ownership");
    let moved = original; // `original` is moved into `moved`

    // println!("{}", original); // this line would fail to compile
    println!("{}", moved); // only `moved` is valid now
}

Copy Types Are the Exception

Simple, fixed-size types stored entirely on the stack — integers, floats, booleans, characters, and tuples composed only of such types — implement the `Copy` trait. Assigning or passing a `Copy` type duplicates the bits instead of moving them, so both the original and the new binding remain valid and usable.

Copy Types Do Not Move

fn main() {
    let x: i32 = 42;
    let y = x; // `x` is copied, not moved

    println!("x = {}, y = {}", x, y); // both remain valid
}

Ownership and Function Calls

Passing a non-`Copy` value into a function moves ownership into that function's parameter. Once the function returns, unless it explicitly returns the value back to the caller, the value is dropped at the end of the function body. This is a frequent surprise for newcomers, and it is the primary motivation for borrowing, covered in the next lesson.

Ownership Moves Into a Function

fn take_ownership(text: String) {
    println!("inside function: {}", text);
} // `text` is dropped here

fn main() {
    let message = String::from("transferred");
    take_ownership(message);

    // println!("{}", message); // fails: value was moved
}
  • Every heap-allocated value has exactly one owning variable at any given time.
  • Assigning a non-Copy value to a new variable moves it; the old variable becomes unusable.
  • Passing a non-Copy value into a function moves it into that function's scope.
  • Returning a value from a function moves ownership back out to the caller.
  • Calling `.clone()` explicitly duplicates heap data, producing two independent owners.

Returning Ownership

A function can hand ownership back to its caller simply by returning the value. This is a common pattern for functions that take ownership of an argument temporarily, transform it, and then give the result back so the caller retains a usable value.

Taking and Giving Back Ownership

fn add_exclamation(mut text: String) -> String {
    text.push('!');
    text
}

fn main() {
    let phrase = String::from("rust is fun");
    let phrase = add_exclamation(phrase); // ownership returns here

    println!("{}", phrase);
}
Note: The most common ownership error for beginners is "use of moved value". It happens whenever a non-Copy variable is used again after being moved into another binding or a function call. The fix is usually to clone the value, restructure the code to borrow instead of move, or return the value from the function that consumed it.
ScenarioEffect on original variable
Assign String to new variableOriginal becomes invalid (moved)
Assign i32 to new variableOriginal stays valid (copied)
Pass String into a functionOriginal becomes invalid (moved into function)
Pass &String into a functionOriginal stays valid (only borrowed)
Note: Ownership rules apply uniformly to every heap-backed type in the standard library, including `Vec<T>`, `HashMap<K, V>`, and `Box<T>`, not just `String`.

Exercise: Rust Ownership

What happens when you assign a `String` variable to another, e.g. `let s2 = s1;`?