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;):
- The moved field (
user.name) becomes uninitialized and cannot be accessed again. - The parent struct as a whole (
user) cannot be used, passed, or printed as a whole value (println!("{user:?}")fails withE0382). - 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