liveBook · Manning

Move

As explained in Learn Rust in a Month of Lunches

Move semantics is the behavior in which transferring a non-Copy value transfers its ownership and prevents use of the original binding.

A move transfers ownership of a value to a new place. For a String, passing it to a function that takes a String gives the function ownership of the same data; Rust does not make another string. The original binding can no longer be used. section 2.7

fn print_country(country_name: String) {
    println!("{country_name}");
}
 
fn main() {
    let country = String::from("Austria");
    print_country(country);                  #1
    print_country(country);                  #2
}

The first call gives print_country ownership of country. The second call tries to use country again, after it has moved. section 2.7

error[E0382]: use of moved value: `country`
 --> src/main.rs:8:19
  |
6 |     let country = String::from("Austria");
  |         ------- move occurs because `country` has type 
  `String`, which does not implement the `Copy` trait
7 |     print_country(country);
  |                   ------- value moved here
8 |     print_country(country);
  |                   ^^^^^^^ value used here after move

The compiler marks both the move and the later use. “Use of moved value” means the original binding no longer owns that value. section 2.7

Choose how the function should receive the value:

  • If it only needs to read the String, pass a reference with &. The caller keeps ownership and can call the function again. section 2.7
  • If it needs to change the caller’s String without taking ownership, use &mut String. section 2.7
  • If a separate owned String is needed, call .clone() explicitly; cloning can use more memory. section 2.8

Not every function argument moves. Types that implement Copy, such as i32, are copied when passed to a function, so the original can be used again. String does not implement Copy. This difference is called copy semantics versus move semantics. section 2.8

The move keyword is useful with closures, especially when starting a thread. A closure that borrows a local variable may outlive that variable; move forces it to take referenced variables by value instead. The compiler’s lifetime error points to this correction. section 11.6.4 section 11.6.2

fn main() {
    let mut my_string = String::from("Can I go inside the thread?");
    let handle = std::thread::spawn(move || {
        println!("{my_string}");
    });
    handle.join().unwrap();
}

Here move makes the spawned closure take the String by value rather than borrow it from main. The thread can then print it without that borrowing problem. section 11.6.4

How Learn Rust in a Month of Lunches covers it

How deep it goes
Walks through a failed function call and its compiler error, then applies move to closures without detailing memory-layout internals.
Examples
Uses a String passed twice to a function, an i32 passed twice, and a spawned closure that prints a String.
What it assumes
Builds from String variables and function calls before introducing ownership transfer, references, and later closure captures.
What you can do after
Diagnose/debug with compiler move errors, then choose borrowing, cloning, or a move closure when the example calls for it.
How it connects
Connects ownership transfers to references, Copy, Clone, closures, and threads.

Also explained in

Compare how 5 books explain move →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Rust in a Month of Lunches. The section links point to the parts of the book it draws on.