Ownership

Partial Moves

A partial move occurs when one or more non-Copy fields of a struct or tuple are moved out into new bindings while the remaining fields stay in place inside the original struct or tuple.

How the Compiler Tracks Partially Moved Structs

When a field of a struct is moved out (let name = user.name;):

  1. The moved field (user.name) becomes uninitialized and cannot be accessed again.
  2. The parent struct as a whole (user) cannot be used, passed, or printed as a whole value (println!("{user:?}") fails with E0382).
  3. All other unmoved fields (user.email, user.age) remain completely valid and can still be read, borrowed, or moved individually!
struct Account {
    name: String,    ──(let n = acc.name;)──> [MOVED to `n`] (acc.name is now invalid!)
    email: String,   ───────────────────────> [Still valid inside `acc.email`!]
    id: u64,         ───────────────────────> [Still valid inside `acc.id`!]
}
(Whole `acc` cannot be used as a single value, but `acc.email` and `acc.id` CAN!)
#[derive(Debug)]
struct Account {
    name: String,
    email: String,
    id: u64,
}

fn main() {
    let acc = Account {
        name: String::from("Ferris"),
        email: String::from("ferris@rust-lang.org"),
        id: 101,
    };

    // Partial move: moves ONLY `acc.name` out of `acc`!
    let extracted_name: String = acc.name;

    // `acc.email` and `acc.id` were NOT moved and remain completely usable:
    println!("Extracted name: '{extracted_name}'");
    println!("Remaining fields: email = '{}', id = {}", acc.email, acc.id);
} // At scope exit, `rustc` drops `extracted_name` AND `acc.email` (skipping `acc.name`)!
Extracted name: 'Ferris'
Remaining fields: email = 'ferris@rust-lang.org', id = 101

Partial Move in Destructuring (ref + Move in the Same Pattern)

During let or match destructuring, you can move one field by value while borrowing another field by reference using the ref keyword:

#[derive(Debug)]
struct Packet {
    header: String,
    payload: Vec<u8>,
}

fn main() {
    let pkt = Packet {
        header: String::from("v1-auth"),
        payload: vec![1, 2, 3, 4],
    };

    // Move `header` by value (`String`), but borrow `payload` by reference (`&Vec<u8>`):
    let Packet { header, ref payload } = pkt;

    println!("Moved header: '{header}', borrowed payload: {payload:?}");
    // Because `pkt.payload` was only borrowed, `pkt.payload` is still owned by `pkt`:
    println!("pkt.payload len is still accessible: {}", pkt.payload.len());
}
Moved header: 'v1-auth', borrowed payload: [1, 2, 3, 4]
pkt.payload len is still accessible: 4

Crucial Restriction: Types Implementing Drop Cannot Be Partially Moved (E0509)

If a struct implements the Drop trait (impl Drop for MyStruct), Rust forbids moving any non-Copy field out of that struct (E0509)! Why? Because Drop::drop(&mut self) receives a mutable reference to the entire struct when it goes out of scope and expects every single field of self to still be initialized:

// ❌ does not compile
struct Connection {
    url: String,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("Closing connection to {}", self.url);
    }
}

fn main() {
    let conn = Connection { url: String::from("tcp://localhost:8080") };
    let stolen_url = conn.url;
}
error[E0509]: cannot move out of type `Connection`, which implements the `Drop` trait
  --> src/main.rs:13:22
   |
13 |     let stolen_url = conn.url;
   |                      ^^^^^^^^
   |                      |
   |                      cannot move out of here
   |                      move occurs because `conn.url` has type `String`, which does not implement the `Copy` trait

error: aborting due to 1 previous error