Chapter 3

Moves and Copy

When I was a kid my parents moved a lot, but I always found them. — Rodney Dangerfield

Most languages let you keep using a variable after you've assigned it somewhere else. For types such as String, Rust rejects this with a compile error.

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 you can no longer use s. With manual memory management, keeping two pointers to the same buffer can cause a use-after-free if one frees the buffer while the other still uses it.

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 the default: move the String without copying its heap buffer. To duplicate the string's buffer, call .clone() explicitly.

Copy Types

Types that can safely be duplicated bit-for-bit can implement Copy. 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

Integers and bool are Copy, while heap-owning values such as String move by default. A type's documentation tells you whether it implements Copy when the distinction is not obvious.

String makes moves visible because it owns a heap allocation. These exercises trace how ownership changes hands. Borrowing lets a function use a value while its caller keeps ownership.

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 now belongs to the called function. It will be dropped when that function finishes unless the function returns it to the caller, as it does here.

Read the signature as String in, String out. The function can choose to mutate the value it owns, but the parameter binding still needs mut before you can change it. Append the text to s, then return the same String so the caller owns it again.

Useful from the Standard Library

  • String::push_str appends a &str to an owned String. It doesn't allocate 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.
    // Only the `format!` version drops the original `s` when the function ends.
    // The `push_str` version returns it, moving ownership back to the caller.
    // Appending may reallocate its buffer, but returning it does not drop it.
    
    #[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. With a Copy type, you get a copy 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. Look at the test: it reads x again after passing it to double.

    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.
    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).
      /// Passing a `String` by value would move it instead.
      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 no longer holds the value; using it again before reassigning it is a compile error.
      • Moving a String transfers its pointer, length, and capacity without copying the heap buffer. To copy the buffer too, call .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 lets a function use a value without taking ownership from its caller.
      Next chapter 4Conditionals and Loops

      Optional Chapters

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

      ← Return to the Rust course curriculum