Ownership

Ownership in Structs and Enums

By default, structs and enums own their fields and variant payloads. When a struct or enum instance is created, ownership of every non-Copy field moves into the struct or enum; when the struct or enum goes out of scope, all of its owned fields are dropped automatically in declaration order.

Owning Types (String, Vec<T>) vs Borrowed Fields (&'a str) in Structs

When designing a struct or enum, you choose whether each field owns its data or borrows data owned elsewhere:

Approach Example Advantages When to Prefer
Owned Fields struct User { name: String } Self-contained; no <'a> lifetime parameters needed; can be returned and stored anywhere freely Most domain models, configs, and long-lived state
Borrowed Fields struct UserView<'a> { name: &'a str } Zero heap allocation or copying when parsing an existing buffer Short-lived parsers, zero-copy views, and iterators
#[derive(Debug)]
struct Article {
    title: String,
    tags: Vec<String>,
}

fn main() {
    let raw_title = String::from("Ownership in Structs");
    let raw_tags = vec![String::from("rust"), String::from("memory")];

    // Ownership of `raw_title` and `raw_tags` moves into `article`:
    let article = Article {
        title: raw_title,
        tags: raw_tags,
    };

    println!("Article owns its fields: {article:?}");
} // Dropping `article` automatically drops `article.title` and `article.tags`!
Article owns its fields: Article { title: "Ownership in Structs", tags: ["rust", "memory"] }

Ownership in Enum Pattern Matching (match vs match &enum)

When matching on an enum that owns non-Copy payload data (such as Message::Write(String)):

  • Matching on msg by value (match msg { Message::Write(text) => ... }) moves the inner String out of msg into text, consuming msg.
  • Matching on &msg by reference (match &msg { Message::Write(text) => ... }) binds text as &String, leaving msg intact!
#[derive(Debug)]
enum Job {
    Run(String),
    Idle,
}

fn main() {
    let job = Job::Run(String::from("backup_db"));

    // 1. Match on `&job` (borrows payload; `job` stays valid!):
    if let Job::Run(name) = &job {
        println!("Inspecting borrowed job name: '{name}'");
    }

    // 2. Match on `job` by value (moves payload out, consuming `job`):
    if let Job::Run(owned_name) = job {
        println!("Consumed owned job name:      '{owned_name}'");
    }
}
Inspecting borrowed job name: 'backup_db'
Consumed owned job name:      'backup_db'