liveBook · Manning

Move

As explained in Rust in Action: Systems programming concepts and techniques

Move semantics define how transferring ownership of a value invalidates or relocates its previous binding instead of implicitly copying the value.

Move semantics transfer ownership instead of implicitly copying a value. The data need not physically move; Rust changes which binding may use it and checks that every use remains valid. section 4.1.1 section 4.4

Ownership can move through several ordinary language operations:

  • Assignment transfers ownership from one binding to another, so the original binding can no longer be used. section 4.4
  • Passing a value as a function argument moves ownership across the function boundary. section 4.4
  • Returning a value can transfer ownership back to the calling scope. section 4.4
  • Using move on a closure makes captured ownership belong to the closure, which may outlive its original scope. section 10.2.1

A CubeSat used after a move

A check_status(sat_a) call moves the CubeSat into the function. Calling check_status(sat_a) again fails with “use of moved value” because CubeSat does not implement Copy. Returning the CubeSat from check_status and rebinding it allows later calls. section 4.1.1 section 4.4

Listing 4.7 Returning ownership back to the original scope
 #![allow(unused_variables)]
 
 #[derive(Debug)]
 struct CubeSat {
   id: u64,
 }
 
 #[derive(Debug)]
 enum StatusMessage {
   Ok,
 }
 
 fn check_status(sat_id: CubeSat) -> CubeSat {
   println!("{:?}: {:?}", sat_id, StatusMessage::Ok);
   sat_id
 }
 
 fn main () {
   let sat_a = CubeSat { id: 0 };
   let sat_b = CubeSat { id: 1 };
   let sat_c = CubeSat { id: 2 };
 
   let sat_a = check_status(sat_a);      #1
   let sat_b = check_status(sat_b);
   let sat_c = check_status(sat_c);
 
   // "waiting" ...
 
   let sat_a = check_status(sat_a);
   let sat_b = check_status(sat_b);
   let sat_c = check_status(sat_c);
 }

Follow the complete CubeSat program to see ownership move into check_status and return to the original scope. section 4.4

Listing 10.13 Using a variable defined in a parent scope in multiple closures
 1 use std::{thread,time};
 2  
 3 fn main() {
 4   let pause = time::Duration::from_millis(20);
 5   let handle1 = thread::spawn(move || {
 6     thread::sleep(pause);
 7    });
 8   let handle2 = thread::spawn(move || {
 9     thread::sleep(pause);
10   });
11  
12   handle1.join();
13   handle2.join();
14 }

Compare two spawned closures that use the same pause value and observe how move changes their captures. section 10.2.6

How Rust in Action: Systems programming concepts and techniques covers it

How deep it goes
Traces assignment, arguments, returns, and closure captures with compiler errors and working listings, but defers deeper capture details.
Examples
Uses CubeSat status checks, a returned CubeSat, compiler diagnostics, and shared pause values in spawned threads.
What it assumes
Assumes basic Rust functions, structs, variables, and closures while teaching ownership movement from concrete examples.
What you can do after
Diagnose/debug with it by reading moved-value and closure-lifetime errors and choosing reassignment, return, Copy, or move.
How it connects
Connects move semantics to ownership, borrowing, lifetimes, closures, threads, and the Copy trait.

Also explained in

Compare how 5 books explain move →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Rust in Action: Systems programming concepts and techniques. The section links point to the parts of the book it draws on.