Chapter 12

Ownership and Borrowing

👋 Anyone can read and edit this exercise. Sign up to save your progress.

Why are Rust developers so frugal? They prefer to borrow.

Ownership is the rule that makes Rust feel different from other languages. The short version:

  1. Every value has exactly one owner.
  2. When the owner goes out of scope, the value is dropped.
  3. You can borrow a value (without taking ownership) by reference.

That's it. The compiler enforces these rules, which is why Rust catches use-after-free, double-free, and data races at compile time, with no garbage collector at runtime.

Move semantics

Assigning or passing a value transfers ownership ("moves" it). After a move, the original binding is no longer usable:

let s = String::from("hello");
let t = s;          // ownership moves from s to t
// println!("{s}"); // ERROR: borrow of moved value
println!("{t}");    // fine

Types that are cheap to copy (integers, bools, chars, fixed-size arrays of those) implement Copy and don't move. They're duplicated bit-for-bit:

let a = 5;
let b = a;      // a is copied, not moved
println!("{a} {b}");

Borrowing

Most of the time you don't want to transfer ownership; you just want to read or modify the value briefly. That's a borrow, written &value (or &mut value for a mutable borrow).

fn length(s: &String) -> usize { s.len() }   // borrow, doesn't take it

let s = String::from("rust");
let n = length(&s);    // borrow s
println!("{s}");       // s still owns the data

The borrow checker enforces one rule that takes a moment to internalize:

At any time, you can have either any number of immutable references or exactly one mutable reference. Never both.

This is what prevents data races at compile time. If the compiler ever yells at you about borrowing, that rule is the first thing to look at.

Taking ownership by value

When a function parameter has an owned type like String (no & in front), calling the function moves the argument in. The caller's binding is no longer usable afterwards. The value lives at the callee now, and will be dropped when the callee finishes (unless it hands ownership back via the return value, which is exactly what happens here).

Note the signature: String in, String out. Implement the body by mutating the parameter (s.push_str(...)) and then returning s. Because you own s, you're free to mutate it without any &mut dance: ownership implies the right to modify.

Useful from the standard library

  • String::push_str appends a &str to an owned String. No allocation if there's spare capacity.
  • The parameter needs mut s: String to call push_str on it. Mutability is a property of the binding, not the type, so even an owned value has to be declared mut before you can mutate it. The mut is local to the function and doesn't appear in the type.
Exercise 1 of 4
Open in Web Editor

Results

    Compiler / runtime output
    
                

    Borrowing without taking

    Most of the time a function only needs to look at a value, not own it. That's a shared borrow, written &T in the signature. The caller keeps ownership; the callee gets temporary read-only access.

    This function takes &str rather than &String. &str is the universal "borrowed string slice" type: a string literal is already a &'static str, and &String automatically coerces to &str, so &str parameters accept both without forcing the caller to convert. Reach for &str by default when you're just reading.

    The body is a one-liner: call .len() on the slice. The point of the exercise is the signature: notice that after the call, the caller's s is still usable in the test below.

    Useful from the standard library

    • str::len is the byte length of the slice. The chapter on strings covers why that's not the same as a character count.
    • The "deref coercion" from &String to &str is what lets the test pass &s directly. No .as_str() needed.
    Exercise 2 of 4
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  

      Mutable borrows

      Sometimes you want to modify a value in place without taking ownership of it. That's a mutable borrow: &mut T. The caller still owns the value, but the callee gets exclusive write access for the duration of the call.

      Two things to notice in the signature:

      1. The parameter is &mut String, not &mut str. We need the owned String because growing it (with push_str) may reallocate; a bare string slice has a fixed length.
      2. There's no return value. The mutation happens through the reference and is visible to the caller after the call returns.

      On the call site (see the test): the caller has to write &mut s explicitly, and s itself has to have been declared let mut s = .... Mutability is opt-in at every layer.

      Useful from the standard library

      • String::push_str works on &mut String exactly the same way as on an owned String. The compiler reaches through the reference for you.
      • String::push is the single-char version, in case you want to append one character at a time.
      Exercise 3 of 4
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    

        Experiments: get the errors on purpose

        Passing the previous tests is the easy part of this chapter. Ownership only really clicks once you've seen the canonical errors with your own eyes, so the messages feel familiar later (chapter 16, chapter 19, ...) instead of like a brick wall.

        Each test below is paired with a commented-out line. Uncomment one at a time, run the tests, read the error carefully, then comment it out again before moving on. The errors are the lesson here. The tests themselves don't assert anything interesting.

        The three errors you'll trigger correspond to the three rules of the borrow checker:

        1. You can't use a value after you've moved it.
        2. You can't have two mutable references to the same value at once.
        3. You can't have a mutable reference while a shared reference is still in use.

        Re-read each compiler message until you can explain in one sentence why the compiler is complaining. That's the muscle this chapter is building.

        Useful from the standard library

        • Clone::clone makes an explicit deep copy when you genuinely need two owners. A good "escape hatch" once you've understood why a borrow won't compile, but not the first thing to reach for.
        • The compiler errors themselves are the documentation here. Each one is a paragraph you'd otherwise have to read in a book.
        Exercise 4 of 4
        Open in Web Editor

        Results

          Compiler / runtime output
          
                      

          Wrapping up ownership and borrowing

          You moved a String into a function and back out, borrowed one read-only as &str, mutated one through &mut String, and made the borrow checker complain on purpose to see its three canonical errors.

          What we learned

          • Every value has exactly one owner. When the owner goes out of scope, the value is dropped. No garbage collector required.
          • Assigning or passing a non-Copy value transfers ownership. The old binding is no longer usable. Copy types (integers, bools, chars, fixed-size arrays of those) are duplicated bit-for-bit instead.
          • Borrows let you read or modify a value without taking ownership. &T is a shared (read-only) borrow; &mut T is exclusive.
          • The borrow-checker rule: at any time, either any number of &T borrows or exactly one &mut T borrow, never both. That's what rules out data races at compile time.
          • Mutability is opt-in at every layer: the binding (let mut x), the parameter (mut s: String or &mut T), and the call site (&mut x).
          • Default to &str over &String (and &[T] over &Vec<T>) for read-only parameters. Slice types accept more callers thanks to deref coercion.
          • clone() is the explicit escape hatch when you really do need two owners. Reach for it after you understand why a borrow won't compile, not before.
          Next chapter 13Structs and Methods