Skip to content
AgileLogics
7 min read

Ownership

Prerequisites: Functions

Ownership is how Rust manages memory without a garbage collector. It's the feature that makes Rust different from most languages, so take this lesson slowly.

Stack and heap, briefly

Values with a fixed, known size — integers, floats, bool, char — are stored directly on the stack, which is fast and cleaned up automatically. Data whose size can change, like the text in a String, lives on the heap: the String itself holds a pointer to heap memory, plus a length and capacity. Heap memory has to be freed exactly once, at the right time. Ownership is Rust's system for getting that right.

The three rules

  1. Each value in Rust has an owner — the variable that holds it.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped (its memory is freed).

Syntax

let a = String::from("hello"); // `a` owns the String
let b = a;                     // ownership MOVES to `b`; `a` is no longer usable
let c = b.clone();             // an explicit deep copy; `b` and `c` each own one

let x = 5;
let y = x;                     // integers are Copy: `x` is still usable

String::from("hello") creates an owned, growable String from a text literal (the Strings lesson covers String in depth).

  • Move — assigning a String to another variable, or passing it to a function, moves ownership. Only the pointer, length, and capacity are copied; the heap data isn't. The old variable becomes invalid, so the text can't be freed twice.
  • Clone — .clone() copies the heap data too, giving you two independent values. It's explicit because it can be expensive.
  • Copy — simple stack-only types (all integers and floats, bool, char, and tuples or arrays made only of these) are copied instead of moved. The original stays usable.
  • Drop — a scope ends at a closing }. When the owner goes out of scope, Rust automatically frees the value. You never call free.

Example

fn main() {
    {
        let temp = String::from("scoped");
        println!("{temp}");
    } // `temp` goes out of scope here and its String is dropped

    let a = String::from("hello");
    let b = a; // move: `a` can't be used after this
    println!("b = {b}");

    let c = b.clone(); // deep copy
    println!("b = {b}, c = {c}");

    let x = 5;
    let y = x; // copy: both stay usable
    println!("x = {x}, y = {y}");

    let name = String::from("Ada");
    takes_ownership(name); // `name` moves into the function

    let n = 10;
    makes_copy(n); // `n` is copied
    println!("n is still {n}");

    let greeting = gives_ownership();
    println!("got back: {greeting}");
}

fn takes_ownership(text: String) {
    println!("took: {text}");
} // `text` is dropped here

fn makes_copy(number: i32) {
    println!("copied: {number}");
}

fn gives_ownership() -> String {
    String::from("hi") // ownership moves out to the caller
}

Expected output

scoped
b = hello
b = hello, c = hello
x = 5, y = 5
took: Ada
copied: 10
n is still 10
got back: hi

Common mistakes

  • Using a value after it moved:
let a = String::from("hello");
let b = a;
println!("{a} {b}");
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:4:16
  |
2 |     let a = String::from("hello");
  |         - move occurs because `a` has type `String`, which does not implement the `Copy` trait
3 |     let b = a;
  |             - value moved here
4 |     println!("{a} {b}");
  |                ^ value borrowed here after move

Read it from the top: where the value moved, why it moved (no Copy), and where you tried to use it. The compiler also suggests a.clone().

  • Using a variable after passing it to a function. Passing a String to a function moves it, exactly like assignment. Either return it from the function, clone it, or — usually best — pass a reference (next lesson).
  • Sprinkling .clone() everywhere to silence errors. It works, but copies data needlessly; references are usually the right fix.

Practice

This program doesn't compile, because word is used after it moves into print_length:

fn print_length(text: String) {
    println!("Length: {}", text.len());
}

fn main() {
    let word = String::from("hello");
    print_length(word);
    println!("Still usable: {word}");
}

Fix it without using .clone(), by having print_length give ownership back to the caller. The fixed program should print:

Length: 5
Still usable: hello
Show solution
fn print_length(text: String) -> String {
    println!("Length: {}", text.len());
    text // return ownership to the caller
}

fn main() {
    let word = String::from("hello");
    let word = print_length(word);
    println!("Still usable: {word}");
}