Structs

Ownership in Struct Fields

A struct that stores owned types (such as String, Vec<T>, or Box<T>) in its fields owns those values directly: the struct controls their lifetime, drops them automatically when the struct itself goes out of scope, and supports partial moves of individual fields.

Struct Ownership and Drop Order

When an owned struct is dropped, Rust drops the struct first and then drops each of its fields in declaration order (from top to bottom as written in the struct definition):

Ownership Hierarchy of `Document`:

  Stack (`doc: Document`)                 Heap Allocations Owned by `doc`
  +-----------------------+
  | id: 1                 |
  | title: String  -------+------------>  "RFC 3495"
  | tags:  Vec<String> ---+------------>  ["rust", "2024"]
  +-----------------------+
  (When `doc` goes out of scope, `title` is dropped first, then `tags`.)
struct Part(&'static str);

impl Drop for Part {
    fn drop(&mut self) {
        println!("Dropping part: {}", self.0);
    }
}

struct Machine {
    first: Part,
    second: Part,
}

fn main() {
    let _machine = Machine {
        first: Part("Engine (declared 1st)"),
        second: Part("Transmission (declared 2nd)"),
    };
    println!("Machine initialized; exiting scope now.");
}
Machine initialized; exiting scope now.
Dropping part: Engine (declared 1st)
Dropping part: Transmission (declared 2nd)

Partial Moves of Struct Fields

Moving a non-Copy field out of a struct performs a partial move. After a partial move:

  • The moved field can no longer be accessed on the original struct.
  • The whole struct cannot be used or passed as a single unit (e.g., println!("{struct_val:?}") or take_whole(struct_val)).
  • All remaining unmoved fields on the struct remain fully accessible and can still be read, borrowed, or moved individually!
State of `user` After `let name = user.username;`:

  user: User (Partially Moved — cannot be used as a whole `User`)
  +-----------------------------------------+
  | id: 42              [VALID - Copy]      |
  | username: String    [MOVED to `name`]   |
  | email: String       [VALID - unmoved]   |
  +-----------------------------------------+
#[derive(Debug)]
struct User {
    id: u64,
    username: String,
    email: String,
}

fn main() {
    let user = User {
        id: 42,
        username: String::from("ada_lovelace"),
        email: String::from("ada@analytical.engine"),
    };

    // Move `username` out of `user` (partial move):
    let extracted_name: String = user.username;

    // `user.id` and `user.email` are still completely valid!
    println!("Extracted name:  {extracted_name}");
    println!("Remaining email: {}", user.email);
    println!("Remaining id:    {}", user.id);
}
Extracted name:  ada_lovelace
Remaining email: ada@analytical.engine
Remaining id:    42

Error: Using a Struct as a Whole After a Partial Move (E0382)

// ❌ does not compile
#[derive(Debug)]
struct User {
    username: String,
    email: String,
}

fn main() {
    let user = User {
        username: String::from("ada"),
        email: String::from("ada@example.com"),
    };

    let _name = user.username; // Partial move occurs here
    println!("{user:?}");      // Error: cannot borrow `user` as a whole
}
error[E0382]: borrow of partially moved value: `user`
  --> src/main.rs:14:15
   |
13 |     let _name = user.username; // Partial move occurs here
   |                 ------------- value partially moved here
14 |     println!("{user:?}");      // Error: cannot borrow `user` as a whole
   |               ^^^^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `user.username` has type `String`, which does not implement the `Copy` trait

Restoring a Partially Moved Struct by Reassigning the Moved Field

If the struct variable is declared mut, assigning a new value to the moved field restores the struct to a fully initialized state so it can once again be used as a whole:

#[derive(Debug)]
struct Packet {
    id: u32,
    payload: String,
}

fn main() {
    let mut pkt = Packet {
        id: 7,
        payload: String::from("initial data"),
    };

    let old_payload = pkt.payload; // `pkt` is now partially moved
    pkt.payload = String::from("replacement data"); // `pkt` is whole again!

    println!("Old payload: {old_payload}");
    println!("Restored packet: {pkt:?}");
}
Old payload: initial data
Restored packet: Packet { id: 7, payload: "replacement data" }

Partial Moves Are Forbidden for Types Implementing Drop (E0509)

If a struct implements the Drop trait directly, Rust forbids moving non-Copy fields out of it because Drop::drop(&mut self) expects every field of self to be valid and initialized when the destructor runs. To extract a field inside Drop or before destruction, wrap the field in Option<T> and call .take(), or use std::mem::take(&mut self.field).