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
msgby value (match msg { Message::Write(text) => ... }) moves the innerStringout ofmsgintotext, consumingmsg. - Matching on
&msgby reference (match &msg { Message::Write(text) => ... }) bindstextas&String, leavingmsgintact!
#[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'