Chapter 6

Borrowing and Ownership

I once heard a Rust joke I liked so much that I borrowed it for this chapter.

In the moves chapter, take_ownership took a String and returned it. That works, but passing ownership back and forth gets tedious fast when the function only needs to read the value.

Instead, a better approach is to let the function "borrow" the value. A borrow lets a function use a value without taking ownership of it: &value for a shared, read-only borrow, and &mut value for an exclusive, writable one.

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

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

The caller keeps ownership the whole time. The function gets temporary access and gives it back when it returns.

Shared or Exclusive Access

For the same value, the borrowing rule is:

While references are in use, you can have either any number of shared & references, or one exclusive &mut reference. Never both at once.

A shared reference promises the data won't change while you're looking at it. A mutable reference promises nobody else is looking while you write to it. For a String, this prevents a write from reallocating the buffer while another reference still points into it. Across threads, the same restriction also helps prevent data races.

Borrowing can take a few tries to get used to, especially when you have to work out where a borrow ends. When the compiler rejects your code, I suggest starting with this rule: who needs to read the value, and who needs to change it?

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. A string literal is already a &str, and a borrowed String automatically becomes one at the call site. That means the same parameter accepts both without making the caller convert anything. Reach for &str by default when you only need to read text.

The body only needs to call .len() on the slice. Pay more attention to the signature and the test: after the call, the caller can still read and grow its owned String because the function only borrowed it.

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.
  • Rust calls the automatic conversion from &String to &str a "deref coercion." A caller with a String can pass &s without calling .as_str(); the tests cover both an owned String and a string literal.
Exercise 1 of 3
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Reveal the full solution Spoiler: the complete answer
    /// Borrows a string reference without taking ownership. The original string
    /// remains valid after this function returns.
    fn borrow_string(s: &str) -> usize {
        s.len()
    }
    
    #[test]
    fn test_borrow_string() {
        let mut s = String::from("The Matrix has you");
        let len = borrow_string(&s);
        // s is still valid here because we only borrowed it
        assert_eq!(len, 18);
        assert_eq!(s, "The Matrix has you");
        s.push_str(", Neo");
        assert_eq!(s, "The Matrix has you, Neo");
        assert_eq!(borrow_string(&s), 23);
    }
    
    #[test]
    fn test_borrow_string_literal() {
        assert_eq!(borrow_string("Ferris"), 6);
    }
    

    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.

    The parameter is &mut String, not &mut str. Appending text may require the owned String to grow its buffer, while a string slice has a fixed length.

    The function does not need to return the String. It changes the value through the reference, and the caller sees that change after the call returns.

    At the call site (see the test), you need both &mut s and a binding declared with 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.
    Exercise 2 of 3
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Reveal the full solution Spoiler: the complete answer
      /// Takes a mutable reference to modify the string in place. The &mut allows us
      /// to change the string's contents.
      fn mutate_string(s: &mut String) {
          s.push_str(" - now with extra crab");
      }
      
      #[test]
      fn test_mutate_string() {
          let mut s = String::from("Ferris");
          mutate_string(&mut s);
          assert_eq!(s, "Ferris - now with extra crab");
      }
      

      Experiments: Break It, Then Fix It

      These tests pass as written. Uncomment the lines in one experiment at a time to see what the compiler rejects. Before you do, predict which line will cause an error and why. Then run the tests and compare the error with your prediction.

      1. In experiment_use_after_move, uncomment the assertion. Fix it without cloning the string or changing take_ownership.
      2. In experiment_two_mutable_borrows, uncomment both lines. Fix the error by reordering the existing statements. Keep both references and both pushes; the final string should be Ferris!!.
      3. In experiment_mix_shared_and_mutable, uncomment the mutation. Reorder the statements, keeping both the print through shared and the mutation. The print should show Ferris; the final string should be Ferris - now with extra crab.

      For the borrowing experiments, don't clone the string, remove an operation, or add a nested scope. Mark the last use of each reference and predict whether your new order will compile before running it. Add an assertion for each final string. Once the tests pass, explain why the borrows no longer conflict even though the reference bindings are still in scope.

      Keep each fix before moving on. To skip an experiment, comment out the lines you uncommented so its test passes again. Even if you run only one test, all tests in the file still have to compile.

      Exercise 3 of 3
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    
        Reveal the full solution Spoiler: the complete answer
        /// Takes ownership of a String and modifies it.
        fn take_ownership(s: String) -> String {
            let mut s = s;
            s.push_str(" - owned by Rust!");
            s
        }
        
        /// Takes a mutable reference to modify the string in place.
        fn mutate_string(s: &mut String) {
            s.push_str(" - now with extra crab");
        }
        
        #[test]
        fn experiment_use_after_move() {
            let s = String::from("Rust");
            let result = take_ownership(s);
            // `s` moved into the function; `result` now owns the returned string.
            assert_eq!(result, "Rust - owned by Rust!");
        }
        
        #[test]
        fn experiment_two_mutable_borrows() {
            let mut s = String::from("Ferris");
            let r1 = &mut s;
            r1.push('!');
            // The borrow through `r1` ends at its last use, before we create `r2`.
            let r2 = &mut s;
            r2.push('!');
            assert_eq!(s, "Ferris!!");
        }
        
        #[test]
        fn experiment_mix_shared_and_mutable() {
            let mut s = String::from("Ferris");
            let shared = &s;
            println!("{shared}");
            // Nothing uses `shared` after this point, so we can borrow `s` mutably.
            mutate_string(&mut s);
            assert_eq!(s, "Ferris - now with extra crab");
        }
        

        Wrapping Up Borrowing

        You borrowed a String read-only as &str, mutated one through &mut String, and practiced repairing ownership and borrowing errors.

        What We Learned

        • A borrow lets you read or modify a value without taking ownership. &T is a shared, read-only borrow; &mut T is an exclusive, writable one.
        • For the same value, active borrows allow either shared &T access or exclusive &mut T access, never both. This restriction also helps prevent data races across threads.
        • For these local references, a borrow ends at the reference's last use, not necessarily at the closing brace. Using r1 before creating r2 lets their exclusive borrows take turns. Printing shared before mutating s ends the shared borrow before the mutable one starts. Using either earlier reference again after the mutation can bring the conflict back.
        • Mutability is opt-in at every layer: the binding (let mut x), the parameter (&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.
        • The compiler errors are part of the lesson. Once you can explain one in a sentence, you can decide what ownership or borrow needs to change instead of guessing.
        Next chapter 7Exercise Break: Word Count

        Optional Chapters

        Extra practice to explore at your own pace. These chapters do not count toward course progress.

        ← Return to the Rust course curriculum