What is partial move in Rust

A partial move in Rust invalidates a struct by moving a non-Copy field out of it, preventing further use of the struct.

The partial move trap

You are building a background worker. Your application struct holds a database connection, a cache, and a configuration string. You want to hand the database connection to the worker thread so it can query data, but you need the configuration to stay in the main loop for logging and shutdown handling. You write let db = app.db; to extract the connection. The compiler rejects the code. It says app is partially moved. You can no longer access app.config. The entire struct is tainted because one field left the building.

This is a partial move. Rust treats structs as atomic units for ownership. You cannot move one field out of a struct and continue using the struct. If a field moves, the struct becomes invalid. The compiler enforces this to prevent memory safety violations during cleanup.

Ownership is atomic

Rust's ownership system relies on a simple rule: every value has exactly one owner. When the owner goes out of scope, the value is dropped. Structs bundle multiple values together. The struct itself is the owner of its fields. When you move a struct, you move all its fields at once.

Imagine a backpack containing a laptop, a notebook, and a water bottle. The backpack is the container. If you hand the backpack to someone else, they get everything inside. You can't hand over just the laptop and keep the backpack valid for the notebook. The backpack's integrity is compromised. Rust applies this logic to memory. If a field moves, the struct can no longer guarantee that all its parts are valid. The compiler marks the struct as unusable.

This rule has one exception: the Copy trait. If a field implements Copy, moving it actually copies the data. The original field remains valid. Primitives like i32, bool, and f64 implement Copy. Complex types like String, Vec, and custom structs do not.

The Copy exception

When you extract a field that implements Copy, the struct survives. The compiler generates a bitwise copy of the field. The original struct retains its data.

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 10, y: 20 };
    
    // i32 implements Copy. This copies the value 10.
    // p1.x remains 10. p1 is still fully valid.
    let x = p1.x;
    
    println!("p1 is still usable: ({}, {})", p1.x, p1.y);
    println!("x is independent: {}", x);
}

The Copy trait is automatic for primitive types. You rarely need to derive it manually for simple data. When you see code that extracts fields without moving the struct, check the field types. They are almost always Copy.

Convention aside: The community often derives Copy and Clone together for data-only structs. If a struct contains only primitives or references, adding #[derive(Copy, Clone)] is standard practice. It signals that the struct is cheap to duplicate and avoids partial move issues entirely.

When the struct breaks

Fields that do not implement Copy move ownership. Extracting such a field moves the data out of the struct. The struct loses ownership of that field. The compiler invalidates the struct to prevent you from accessing the missing data.

struct Data {
    text: String,
    num: i32,
}

fn main() {
    let d1 = Data {
        text: String::from("hello"),
        num: 5,
    };
    
    // String does not implement Copy.
    // This moves the String out of d1.text.
    // d1 is now partially moved and invalid.
    let text = d1.text;
    
    // ERROR: E0382 (use of moved value)
    // The compiler rejects this because d1 is partially moved.
    // println!("num is {}", d1.num);
    
    println!("text is {}", text);
}

The error is E0382 (use of moved value). The compiler tracks the validity of d1. Once d1.text moves, d1 is marked as moved. Any attempt to use d1 triggers the error. The compiler does not care that you only want to read d1.num. The struct is broken.

Why Rust forbids this

The restriction exists because of drop glue. When a struct goes out of scope, Rust generates code to drop each field. This drop code assumes all fields are valid. If a field has been moved, the drop code would try to free memory that no longer belongs to the struct. This causes double-free errors or use-after-free bugs.

Rust could theoretically allow partial moves and track which fields are valid, but the complexity is high. Drop implementations might access multiple fields. Methods might assume the struct is complete. The atomic ownership model keeps the compiler simple and the guarantees strong. If a struct is valid, all its fields are valid. If a field moves, the struct is invalid.

Convention aside: Keep unsafe blocks small. The community calls this the "minimum unsafe surface" rule. Partial moves are a safe-language concern, but understanding them helps you write safe abstractions. When you build a custom collection, you need to know that moving an element invalidates the container unless you handle the state carefully.

The Option escape hatch

You can extract a non-Copy field and keep the struct alive by replacing the field with a placeholder. The standard pattern uses Option. Wrap the field in Option<T>. Use Option::take to move the value out, leaving None behind. The struct remains valid because the field still exists; it just holds None.

struct App {
    db: Option<Database>,
    config: String,
}

fn main() {
    let mut app = App {
        db: Some(Database::new()),
        config: String::from("production"),
    };
    
    // take() moves the Database out of app.db.
    // app.db becomes None.
    // app remains valid because the field is still present.
    let db = app.db.take();
    
    // app is usable. app.db is None, app.config is intact.
    println!("Config: {}", app.config);
    
    // db owns the Database now.
    db.query("SELECT 1");
}

This pattern is common for resources that need to be extracted. You wrap the resource in Option. You initialize it with Some. You extract it with take. The struct survives.

For types that implement Default, you can use std::mem::take. It works like Option::take but replaces the value with Default::default() instead of None.

use std::mem;

struct Cache {
    entries: Vec<String>,
    size: usize,
}

impl Default for Cache {
    fn default() -> Self {
        Cache {
            entries: Vec::new(),
            size: 0,
        }
    }
}

struct Service {
    cache: Cache,
    name: String,
}

fn main() {
    let mut service = Service {
        cache: Cache {
            entries: vec!["key".to_string()],
            size: 1,
        },
        name: String::from("worker"),
    };
    
    // mem::take moves the Cache out.
    // service.cache is replaced with Cache::default().
    // service remains valid.
    let cache = mem::take(&mut service.cache);
    
    println!("Service name: {}", service.name);
    println!("Cache entries: {}", cache.entries.len());
}

std::mem::take is the idiomatic way to extract resources without Option. It requires Default. It keeps the struct usable.

Methods that consume self

A common pitfall involves methods that take self by value. When you call a method that consumes self, the entire struct moves. This happens even if the method only uses one field.

struct Data {
    text: String,
    num: i32,
}

impl Data {
    // This method takes ownership of the whole struct.
    fn process(self) -> String {
        // Only uses self.text, but self.num is also moved.
        self.text.to_uppercase()
    }
}

fn main() {
    let d = Data {
        text: String::from("hello"),
        num: 5,
    };
    
    // d is moved into process().
    // d is no longer usable.
    let result = d.process();
    
    // ERROR: E0382 (use of moved value)
    // println!("num is {}", d.num);
}

The method signature fn process(self) moves the struct. If you need to keep the struct alive, change the signature to fn process(&self) or fn process(&mut self). Borrowing avoids the move.

Decision matrix

Use references when you need to read or modify a field but the struct must remain usable. Borrowing keeps ownership with the struct.

Use Clone when the data is cheap to copy and you want an independent snapshot. Cloning creates a new value without moving the original.

Move the entire struct when the destination needs full ownership and the source is done. Moving the struct transfers all fields at once.

Use Option::take or std::mem::take when you must extract a non-Copy field and keep the struct alive for other fields. This replaces the field with a placeholder and avoids partial move errors.

Treat the struct as a single unit. If you need a piece, borrow it. If you need the piece forever, move the whole thing or use Option::take. The compiler will guide you. Trust the error messages. They point to the exact field causing the issue.

Where to go next