SyntaxStudy
Sign Up
Rust Move Semantics in Depth
Rust Beginner 1 min read

Move Semantics in Depth

Move semantics in Rust mean that assigning a non-`Copy` value to a new variable or passing it to a function transfers ownership — the original binding becomes invalid. This is different from shallow copies in other languages: in Rust, the compiler guarantees at the type system level that a moved-from value cannot be used again, preventing use-after-free without any runtime checks. Moves are zero-cost at runtime for types stored on the heap: only the pointer, length, and capacity fields on the stack are copied; the heap data stays in place. For types allocated entirely on the stack, the compiler may still choose to copy the bytes, but logically the ownership has transferred. The `Copy` trait explicitly opts a type into copy semantics, and attempting to implement `Copy` on a type that contains a non-`Copy` field is a compile error. Understanding when a move occurs is key to writing ergonomic Rust. Moves happen on assignment, on function argument passing, on function return, and when pattern-matching moves a field out of a struct. If you need to use a value after passing it somewhere, you have three options: clone it, pass a reference instead, or redesign the API to return ownership back to the caller.
Example
#[derive(Debug)]
struct Buffer {
    data: Vec<u8>,
}

// Takes ownership of buf — caller can no longer use it
fn process(buf: Buffer) -> usize {
    println!("processing {} bytes", buf.data.len());
    buf.data.len()
}

// Returns ownership back so the caller can continue using it
fn inspect(buf: Buffer) -> Buffer {
    println!("first byte: {:?}", buf.data.first());
    buf  // move buf back to the caller
}

fn main() {
    let buf = Buffer { data: vec![1, 2, 3, 4, 5] };

    // Pass to inspect — ownership goes in and comes back
    let buf = inspect(buf);
    println!("still have buf: {:?}", buf);

    // Pass to process — ownership is consumed; buf is gone
    let len = process(buf);
    // println!("{:?}", buf); // compile error: use of moved value
    println!("processed {len} bytes");

    // Demonstrate that Vec<u8> is not Copy
    let v1 = vec![10u8, 20, 30];
    let v2 = v1;    // move
    // let _ = v1;  // compile error
    println!("v2 = {:?}", v2);

    // Cloning avoids the move
    let v3 = v2.clone();
    println!("v2={:?}  v3={:?}", v2, v3);
}