Chapter 3

Moves and Copy

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

Most languages let you keep using a variable after you've assigned it somewhere else. Rust usually doesn't, and it stops you at compile time.

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

Assigning s to t moves the string. There's now one owner, t, and s is dead. Reach for s again and the compiler refuses, naming the exact line. In a language with shared mutable pointers this would be a silent bug (two variables aliasing one buffer, one of them freeing it first), and Rust turns it into a compile error.

Why move instead of copy? A String owns a buffer on the heap. Copying it on every assignment would mean duplicating that buffer over and over, silently. Rust makes the cheap thing (a move: hand over the pointer) the default and the expensive thing (a deep copy with .clone()) something you ask for out loud.

Copy types

Small values that live entirely on the stack don't have this problem. Integers, bool, char, and fixed-size arrays of them implement the Copy trait, so assigning one duplicates the bits instead of moving:

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

The rule of thumb: a type that owns nothing on the heap and is cheap to duplicate is Copy, and the move rules never bite it. Everything else moves.

This is the first chapter where moves matter, because String is the first heap-owning type you've met. Borrowing, using a value without taking it, is the next chapter. Here we just get comfortable with ownership changing hands.

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 directly: 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 2
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Reveal the full solution Spoiler: the complete answer
    /// Takes ownership of a String and modifies it.
    /// When you pass a String to this function, ownership transfers.
    fn take_ownership(s: String) -> String {
        let mut s = s;
        s.push_str(" - owned by Rust!");
        s
    }
    
    // Alternative:
    //
    //     fn take_ownership(s: String) -> String {
    //         format!("{s} - owned by Rust!")
    //     }
    //
    // This reuses `s` only to read it, and builds a brand-new String for the
    // result. Under the hood `format!` expands to roughly:
    //
    //     let mut buf = String::new();
    //     buf.write_fmt(format_args!("{s} - owned by Rust!")).unwrap();
    //     buf
    //
    // So instead of growing the moved-in buffer in place (the `push_str`
    // version), it allocates a fresh buffer and writes both pieces into it.
    // The original `s` is dropped at the end of the function either way.
    
    #[test]
    fn test_take_ownership() {
        let s = String::from("Rust");
        let result = take_ownership(s);
        // Note: s is no longer valid here! It was moved.
        assert_eq!(result, "Rust - owned by Rust!");
    }
    

    Copy types don't move

    take_ownership moved a String. This step shows the other half of the rule: a Copy type is duplicated instead, so the caller keeps its value.

    double takes an i32 by value. Because i32 is Copy, the caller's variable is still alive after the call. The body is one expression; the lesson is in the test, where x is read again after being passed in.

    Useful from the standard library

    • i32, the other integer types, bool, char, and fixed-size arrays of Copy values all implement Copy. Assigning or passing one duplicates its bits instead of moving it.
    • Heap-owning types like String and Vec<T> are deliberately not Copy. When you need a second owner of one of those, ask for it with Clone::clone.
    Exercise 2 of 2
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Reveal the full solution Spoiler: the complete answer
      /// Doubles a number.
      ///
      /// `i32` is a `Copy` type, so the caller's value is duplicated bit-for-bit
      /// when it's passed in. The original stays usable after the call (see the
      /// test), which is exactly what does *not* happen with a `String`.
      fn double(n: i32) -> i32 {
          n * 2
      }
      
      #[test]
      fn test_double() {
          let x = 21;
          let y = double(x);
          assert_eq!(y, 42);
          // x is still usable here: i32 is Copy, so `double(x)` copied it
          // instead of moving it.
          assert_eq!(x, 21);
      }
      

      Wrapping up moves and Copy

      You moved a String into a function and back out, and saw that an i32 copies instead of moving.

      What we learned

      • Assigning or passing a non-Copy value moves it. The old binding is dead, and using it again is a compile error, not a runtime surprise.
      • A move hands over ownership cheaply (just the pointer). Rust makes deep copies explicit through .clone().
      • Copy types (integers, bool, char, fixed-size arrays of those) duplicate bit-for-bit instead of moving, so the original stays usable.
      • The owner is responsible for the value: when it goes out of scope, the value is dropped, with no garbage collector involved.
      • Borrowing, coming up next, lets you hand a value to a function without giving up ownership at all.
      Next chapter 4Conditionals and loops