Rust 43 ๐ฆ Lifetime Annotations in Functions and Structs
The previous chapter introduced lifetimes as the compiler’s mechanism for preventing dangling references. This chapter is about the syntax and the patterns. The 'a annotation is not a runtime value and it is not a memory address. It is a name for a region of code, and the compiler uses it to reason about how long a reference is valid. The annotation appears in three places: on function signatures, on struct definitions, and on impl blocks. Each place has its own rules and its own common patterns, and the elision rules cover the majority of cases.
The function signature is where lifetimes are most visible. A function that takes one reference and returns one reference does not need an annotation: the elision rules assign the input’s lifetime to the output. A function that takes multiple references and returns a reference needs an annotation, because the compiler cannot know which input the output borrows from. The annotation is a contract: fn longest<'a>(x: &'a str, y: &'a str) -> &'a str says that the returned reference is valid for the lifetime that is the intersection of the two inputs. The caller relies on this, and the borrow checker enforces it.
The struct definition is where lifetimes become part of the type. A struct that owns its data has no lifetime parameter. A struct that borrows its data has a lifetime parameter for each distinct borrow. struct Excerpt<'a> { text: &'a str } says that the struct borrows a string, and the struct cannot outlive the string. The lifetime parameter is part of the struct’s type, so Excerpt<'a> and Excerpt<'b> are different types when 'a and 'b are different. The compiler tracks the relationship, and it rejects a program where the struct outlives the borrowed data.
The impl block is where the struct’s lifetime is used in methods. The impl<'a> Excerpt<'a> block declares the lifetime parameter, and the methods inside it take &self, which has the lifetime of the struct. The elision rules assign &self‘s lifetime to the returned reference, so a method that returns a reference from the struct’s field does not need an annotation. A method that takes an additional reference and returns a reference needs an annotation to say which lifetime the output borrows from.
This chapter covers three areas. First, the annotation syntax and the elision rules โ the 'a syntax, the three elision rules, and the cases where the annotation is required. Second, lifetime annotations in functions โ the single-input case, the multiple-input case, the 'static lifetime, and the patterns for returning references. Third, lifetime annotations in structs and impl blocks โ the struct definition, the impl block, the methods, and the interaction with generics. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the annotation’s role.
Key point: A lifetime annotation is a name for a region of code, not a runtime value. The elision rules infer the lifetimes for most functions: one lifetime per input, one input lifetime assigned to the output, and &self assigned to the output. The explicit annotation is required when a function takes multiple references and returns a reference, when a struct holds a reference, and when a method’s output borrows from a parameter other than &self.
Why lifetime annotations exist
The ambiguity problem. A function that takes one reference and returns one reference has an unambiguous lifetime: the output borrows from the input. A function that takes two references and returns one reference is ambiguous: the output could borrow from either input. The compiler cannot guess, so the developer must write the annotation. The annotation resolves the ambiguity by naming the relationship between the inputs and the output.
The contract problem. A function’s signature is a contract with the caller. The caller needs to know how long the returned reference is valid. The annotation is the way to express this. fn first<'a>(s: &'a str) -> &'a str says that the returned reference is valid for as long as the input reference is valid. The caller can rely on this, and the compiler enforces it. Without the annotation, the contract would be implicit, and the caller would have to read the function body to understand it.
The struct-type problem. A struct that holds a reference must declare the lifetime as a type parameter. The lifetime is part of the struct’s type, and it determines how long the struct can live. struct Excerpt<'a> { text: &'a str } says that the struct borrows a string with lifetime 'a, and the struct cannot outlive 'a. The compiler uses this information to verify that the struct is not used after the borrowed data is dropped.
The method-lifetime problem. A method on a struct with a lifetime parameter must declare the lifetime in the impl block. The &self parameter has the struct’s lifetime, and the elision rules assign it to the output. A method that takes an additional reference and returns a reference needs an annotation to say whether the output borrows from &self or from the parameter. The annotation is the way to express this.
The generic-interaction problem. A generic function can have both type parameters and lifetime parameters. The order matters: lifetimes come first, then types. fn f<'a, T>(x: &'a T) -> &'a T declares the lifetime 'a and the type T. The lifetime bound T: 'a says that the type T outlives the lifetime 'a, which is required when the function returns a reference to a T.
The trade-off. Lifetime annotations add noise to function signatures. A function with multiple references and a returned reference has an annotation that is not always obvious. The borrow checker rejects some programs that are actually safe, and the fix may require restructuring the code or adding a bound. The trade-off is between the safety guarantee and the expressiveness of the type system. For most programs, the elision rules keep the annotations minimal, and the annotations that are required are a small price for the absence of dangling references.
a. The annotation syntax and the elision rules
A lifetime annotation is written with an apostrophe and a name, conventionally a single lowercase letter starting with 'a. The annotation is attached to a reference with &'a T. The annotation can also appear as a bound on a generic parameter: T: 'a means that T outlives 'a.
fn first<'a>(s: &'a str) -> &'a str {
&s[..1]
}
The function declares a lifetime parameter 'a in the angle brackets, uses it on the input reference, and uses it on the output reference. The contract is that the returned reference is valid for the same lifetime as the input.
The elision rules allow the annotation to be omitted in common cases. There are three rules:
- Each input reference gets its own lifetime parameter. A function with two input references has two lifetime parameters, even if they are not written.
- If there is exactly one input lifetime, it is assigned to all output lifetimes. A function with one input reference and one output reference has the same lifetime for both.
- If there is a
&selfor&mut selfparameter, its lifetime is assigned to all output lifetimes. A method that returns a reference from&selfdoes not need an annotation.
The first rule means that fn f(x: &str, y: &str) expands to fn f<'a, 'b>(x: &'a str, y: &'b str). The second rule means that fn f(x: &str) -> &str expands to fn f<'a>(x: &'a str) -> &'a str. The third rule means that a method fn get(&self) -> &str expands to fn get<'a>(&'a self) -> &'a str.
The annotation is required when none of the rules apply. A function with two input references and a returned reference has no rule that assigns the output’s lifetime, so the annotation is required.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
The annotation says that both inputs and the output share the lifetime 'a. The returned reference is valid for the intersection of the two input lifetimes. The caller must ensure that both inputs are valid for as long as the returned reference is used.
A function can have multiple lifetime parameters with different bounds:
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
The function returns a reference with the lifetime 'a, which is the lifetime of x. The y parameter has the lifetime 'b, which is unrelated to the output. The caller can pass a y with a shorter lifetime, and the returned reference is still valid for the lifetime of x.
b. Lifetime annotations in functions
The function signature is the most common place for lifetime annotations. The patterns fall into a few categories.
The single-input case. A function with one input reference and one output reference does not need an annotation, because the elision rules assign the input’s lifetime to the output.
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
The elided form is fn first_word<'a>(s: &'a str) -> &'a str. The function returns a reference that borrows from s, and the caller must ensure that s outlives the returned reference.
The multiple-input case. A function with multiple input references and a returned reference needs an annotation. The annotation says which input the output borrows from.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
The annotation 'a is shared by both inputs and the output. The returned reference is valid for the shorter of the two input lifetimes. The caller must ensure that both inputs are valid for as long as the returned reference is used.
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
The annotation 'a is on x and the output, and 'b is on y. The returned reference is valid for the lifetime of x only. The y parameter has an unrelated lifetime, and the caller can pass a y that does not outlive the returned reference.
The 'static case. A function that returns a 'static reference returns a reference that is valid for the entire program. String literals have the 'static lifetime.
fn greeting() -> &'static str {
"hello"
}
A 'static reference can be used anywhere, because it is valid for the entire execution. The 'static lifetime is also used as a bound: T: 'static means that the type T contains no references with a shorter lifetime.
The method case. A method that takes &self and returns a reference does not need an annotation, because the elision rules assign &self‘s lifetime to the output.
impl Excerpt<'_> {
fn text(&self) -> &str {
self.text
}
}
The elided form is fn text<'a>(&'a self) -> &'a str. The returned reference borrows from self, and the caller must ensure that the struct outlives the returned reference.
A method that takes an additional reference and returns a reference needs an annotation to say which lifetime the output borrows from:
impl<'a> Excerpt<'a> {
fn longer<'b>(&'a self, other: &'b str) -> &'a str {
if self.text.len() > other.len() {
self.text
} else {
self.text
}
}
}
The method returns a reference with the lifetime 'a, which is the lifetime of self. The other parameter has the lifetime 'b, which is unrelated to the output. The caller can pass an other with a shorter lifetime.
c. Lifetime annotations in structs and impl blocks
A struct that holds a reference must declare a lifetime parameter. The parameter is used in the field’s type, and the struct’s lifetime is bounded by the referenced data.
struct Excerpt<'a> {
text: &'a str,
}
The Excerpt struct holds a reference to a string with lifetime 'a. The struct cannot outlive the string. The compiler enforces this: the struct’s lifetime is bounded by 'a.
The struct is used with a concrete lifetime inferred from the context:
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first_sentence };
println!("{}", excerpt.text);
}
The excerpt variable borrows from novel. The compiler infers the lifetime 'a as the region of code where excerpt is used. The novel variable must be valid for that region, and the compiler verifies this.
An impl block on a struct with a lifetime must declare the lifetime parameter:
impl<'a> Excerpt<'a> {
fn text(&self) -> &str {
self.text
}
fn first_word(&self) -> &str {
self.text.split_whitespace().next().unwrap_or("")
}
}
The impl<'a> declares the lifetime parameter, and the methods inside the block use it. The &self parameter has the lifetime of the struct, and the elision rules assign it to the returned reference. The methods do not need an explicit annotation because the elision rules apply.
A struct can have multiple lifetime parameters if it holds references with different lifetimes:
struct Pair<'a, 'b> {
first: &'a str,
second: &'b str,
}
The Pair struct holds two references with potentially different lifetimes. The compiler tracks both and ensures that the struct does not outlive either.
A struct can have both lifetime parameters and type parameters:
struct Holder<'a, T> {
value: &'a T,
}
The Holder struct holds a reference to a value of type T. The lifetime 'a is the lifetime of the reference, and T is the type of the value. The compiler tracks both. The bound T: 'a is implied when the struct holds a reference to T, because the reference cannot outlive the value.
The 'static bound on a struct means that the struct contains no references with a shorter lifetime:
struct Config {
name: &'static str,
version: &'static str,
}
The Config struct holds two string literals. The 'static lifetime says that the strings are valid for the entire program. The struct can be used anywhere.
Complete Example Session
// ============================================
// PART 1: A SINGLE-INPUT FUNCTION
// ============================================
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn main() {
let text = String::from("hello world");
let word = first_word(&text);
println!("{}", word); // hello
}
// The elision rules assign the input's lifetime to the output.
// No annotation is needed.
// ============================================
// PART 2: A MULTIPLE-INPUT FUNCTION
// ============================================
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
let s2 = String::from("short");
let result = longest(&s1, &s2);
println!("{}", result); // long string
}
// The annotation 'a is shared by both inputs and the output.
// The returned reference is valid for the shorter lifetime.
// ============================================
// PART 3: MULTIPLE LIFETIME PARAMETERS
// ============================================
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
fn main() {
let s1 = String::from("first");
let s2 = String::from("second");
let result = first(&s1, &s2);
println!("{}", result); // first
}
// The output borrows from x only.
// y has an unrelated lifetime 'b.
// ============================================
// PART 4: THE 'static LIFETIME
// ============================================
fn greeting() -> &'static str {
"hello"
}
fn main() {
let s = greeting();
println!("{}", s); // hello
}
// String literals have the 'static lifetime.
// The returned reference is valid for the entire program.
// ============================================
// PART 5: A STRUCT WITH A LIFETIME
// ============================================
struct Excerpt<'a> {
text: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first_sentence };
println!("{}", excerpt.text);
}
// The Excerpt struct borrows from novel.
// The struct cannot outlive novel.
// ============================================
// PART 6: AN IMPL BLOCK WITH A LIFETIME
// ============================================
impl<'a> Excerpt<'a> {
fn text(&self) -> &str {
self.text
}
fn first_word(&self) -> &str {
self.text.split_whitespace().next().unwrap_or("")
}
}
// The impl block declares the lifetime parameter.
// The methods take &self and return &str.
// The elision rules assign &self's lifetime to the output.
// ============================================
// PART 7: A METHOD WITH AN ADDITIONAL LIFETIME
// ============================================
impl<'a> Excerpt<'a> {
fn longer<'b>(&'a self, other: &'b str) -> &'a str {
if self.text.len() > other.len() {
self.text
} else {
self.text
}
}
}
// The method returns a reference with the lifetime 'a,
// which is the lifetime of self.
// The other parameter has the lifetime 'b, unrelated to the output.
// ============================================
// PART 8: MULTIPLE LIFETIMES IN A STRUCT
// ============================================
struct Pair<'a, 'b> {
first: &'a str,
second: &'b str,
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
let pair = Pair { first: &s1, second: &s2 };
println!("{} {}", pair.first, pair.second);
}
// The Pair struct holds two references with different lifetimes.
// The compiler tracks both.
// ============================================
// PART 9: A STRUCT WITH A LIFETIME AND A TYPE
// ============================================
struct Holder<'a, T> {
value: &'a T,
}
fn main() {
let x = 42;
let holder = Holder { value: &x };
println!("{}", holder.value);
}
// The Holder struct holds a reference to a value of type T.
// The lifetime 'a is the lifetime of the reference.
// The type T is the type of the value.
// The bound T: 'a is implied.
// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================
struct Parser<'a> {
input: &'a str,
position: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input, position: 0 }
}
fn rest(&self) -> &'a str {
&self.input[self.position..]
}
fn peek(&self) -> Option<char> {
self.rest().chars().next()
}
fn advance(&mut self) -> Option<char> {
let c = self.peek()?;
self.position += c.len_utf8();
Some(c)
}
}
fn main() {
let input = String::from("hello world");
let mut parser = Parser::new(&input);
while let Some(c) = parser.advance() {
print!("{}", c);
}
println!();
}
// The Parser struct holds a reference to the input.
// The rest method returns a reference with the input's lifetime,
// not &self's lifetime, because the slice borrows from the input.
// The advance method takes &mut self and does not return a reference.
The ten parts show a single-input function, a multiple-input function, multiple lifetime parameters, the 'static lifetime, a struct with a lifetime, an impl block with a lifetime, a method with an additional lifetime, multiple lifetimes in a struct, a struct with a lifetime and a type, and the complete example.
Quick Reference
Annotation Syntax
| Form | Meaning |
|---|---|
<'a> | Declare a lifetime parameter |
&'a T | A reference with lifetime 'a |
T: 'a | The type T outlives 'a |
&'a mut T | A mutable reference with lifetime 'a |
'static | The lifetime of the entire program |
The Elision Rules
| Rule | Effect |
|---|---|
| 1 | Each input reference gets its own lifetime |
| 2 | One input lifetime โ assigned to the output |
| 3 | &self lifetime โ assigned to the output |
Function Signatures
| Signature | Annotation needed |
|---|---|
fn f(x: &str) -> &str | No (rule 2) |
fn f(x: &str, y: &str) -> &str | Yes (ambiguous) |
fn f<'a>(x: &'a str, y: &'a str) -> &'a str | Explicit |
fn f<'a, 'b>(x: &'a str, y: &'b str) -> &'a str | Explicit, different lifetimes |
fn f() -> &'static str | 'static |
Struct and Impl
| Form | Meaning |
|---|---|
struct S<'a> { f: &'a T } | A struct with a lifetime |
impl<'a> S<'a> { ... } | An impl block with a lifetime |
impl S<'_> { ... } | Anonymous lifetime in the impl |
struct S<'a, T> { f: &'a T } | Lifetime and type parameters |
struct S { f: &'static str } | A 'static field |
Best Practices
โ Do This:
// Let the elision rules handle simple cases
fn first(s: &str) -> &str { &s[..1] } // โ
// Add annotations for multiple inputs
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... } // โ
// Use separate lifetimes when the output borrows from one input
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x } // โ
// Declare the lifetime in a struct that borrows
struct Excerpt<'a> { text: &'a str } // โ
// Use the lifetime in the impl block
impl<'a> Excerpt<'a> { fn text(&self) -> &str { self.text } } // โ
// Use 'static for string literals
fn greeting() -> &'static str { "hello" } // โ
โ Don’t Do This:
// Don't omit the annotation with multiple inputs
fn longest(x: &str, y: &str) -> &str { ... } // missing lifetime // โ
// Don't use the same lifetime when the output borrows from one input
fn first<'a>(x: &'a str, y: &'a str) -> &'a str { x } // too restrictive // โ
(works but over-constrains)
// Don't forget the lifetime in the impl block
impl Excerpt<'_> { ... } // anonymous lifetime is fine in modern Rust // โ
// Don't return a reference to a local
fn dangling() -> &str { let s = String::new(); &s } // โ
// Don't use 'static when a shorter lifetime suffices
fn f(s: &str) -> &'static str { s } // does not compile // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Missing lifetime specifier | Multiple inputs, returned reference | Add the annotation |
| Borrowed value does not live long enough | Reference outlives the data | Extend the scope or own the data |
| Over-constraining lifetimes | Same lifetime for all inputs | Use separate lifetimes |
| Struct outlives the data | The struct’s lifetime is longer | Declare the data in an outer scope |
| Method returns the wrong lifetime | Output borrows from a parameter | Annotate the method |
'static bound not satisfied | Borrowed data escapes | Move the data into the closure |
| Impl block missing the lifetime | The struct has a lifetime parameter | Add impl<'a> S<'a> |
Real-World Examples
1. Single-Input Function
fn first_word(s: &str) -> &str { s.split_whitespace().next().unwrap_or("") }
2. Multiple-Input Function
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
3. Separate Lifetimes
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x }
4. ‘static
fn greeting() -> &'static str { "hello" }
5. Struct with a Lifetime
struct Excerpt<'a> { text: &'a str }
6. Impl Block
impl<'a> Excerpt<'a> { fn text(&self) -> &str { self.text } }
7. Method with an Additional Lifetime
impl<'a> Excerpt<'a> { fn longer<'b>(&'a self, other: &'b str) -> &'a str { self.text } }
8. Multiple Lifetimes in a Struct
struct Pair<'a, 'b> { first: &'a str, second: &'b str }
9. Lifetime and Type Parameters
struct Holder<'a, T> { value: &'a T }
10. Complete Parser
struct Parser<'a> { input: &'a str, position: usize }
impl<'a> Parser<'a> { fn rest(&self) -> &'a str { ... } }
Visual
The Elision Rules
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE ELISION RULES โ
โ โ
โ RULE 1: Each input reference gets its own lifetime. โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ fn f(x: &str, y: &str) โ โ
โ โ โ fn f<'a, 'b>(x: &'a str, y: &'b str) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ RULE 2: One input lifetime โ assigned to the output. โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ fn f(x: &str) -> &str โ โ
โ โ โ fn f<'a>(x: &'a str) -> &'a str โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ RULE 3: &self lifetime โ assigned to the output. โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ impl S { fn m(&self) -> &str } โ โ
โ โ โ impl S { fn m<'a>(&'a self) -> &'a str } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ AMBIGUOUS: multiple inputs, returned reference โ โ
โ โ fn f(x: &str, y: &str) -> &str โ โ
โ โ โ annotation REQUIRED โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
A Function with Multiple Inputs
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ A FUNCTION WITH MULTIPLE INPUTS โ
โ โ
โ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { โ
โ if x.len() > y.len() { x } else { y } โ
โ } โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 'a: the shared lifetime of both inputs and the outputโ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโ โ โ
โ โ โ x: &'a str โ โโโ โ โ
โ โ โโโโโโโโโโโโโโโโ โ โ โ
โ โ โโโโบ return: &'a str โ โ
โ โ โโโโโโโโโโโโโโโโ โ โ โ
โ โ โ y: &'a str โ โโโ โ โ
โ โ โโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ The returned reference is valid for the SHORTER โ โ
โ โ of the two input lifetimes. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Caller: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ let s1 = String::from("long string"); โ โ
โ โ let result; โ โ
โ โ { โ โ
โ โ let s2 = String::from("short"); โ โ
โ โ result = longest(&s1, &s2); โ โ
โ โ } โ โ
โ โ println!("{}", result); โ ERROR: s2 is dropped โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The borrow checker rejects the program because the โ
โ returned reference would outlive s2. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
A Struct with a Lifetime
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ A STRUCT WITH A LIFETIME โ
โ โ
โ struct Excerpt<'a> { โ
โ text: &'a str, โ
โ } โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 'a: the lifetime of the borrowed string โ โ
โ โ โ โ
โ โ The struct cannot outlive the string. โ โ
โ โ The borrow checker enforces this. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ impl<'a> Excerpt<'a> { โ
โ fn text(&self) -> &str { โ
โ self.text โ
โ } โ
โ } โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ The impl block declares the lifetime parameter. โ โ
โ โ The &self parameter has the lifetime of the struct. โ โ
โ โ The elision rules assign it to the output. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ let novel = String::from("Call me Ishmael..."); โ โ
โ โ let first = novel.split('.').next().unwrap(); โ โ
โ โ let excerpt = Excerpt { text: first }; โ โ
โ โ โ โ
โ โ novel: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ โ
โ โ excerpt: โโโโโโโโโโโโโโโโโบ โ โ
โ โ โ โ
โ โ excerpt is dropped before novel. โ โ
โ โ The program compiles. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
A Method with an Additional Lifetime
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ A METHOD WITH AN ADDITIONAL LIFETIME โ
โ โ
โ impl<'a> Excerpt<'a> { โ
โ fn longer<'b>(&'a self, other: &'b str) -> &'a str { โ
โ if self.text.len() > other.len() { โ
โ self.text โ
โ } else { โ
โ self.text โ
โ } โ
โ } โ
โ } โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 'a: the lifetime of self and the output โ โ
โ โ 'b: the lifetime of the other parameter โ โ
โ โ โ โ
โ โ The output borrows from self, not from other. โ โ
โ โ The caller can pass an other with a shorter โ โ
โ โ lifetime, and the returned reference is still โ โ
โ โ valid for the lifetime of self. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ let excerpt = Excerpt { text: "hello" }; โ โ
โ โ let result = excerpt.longer("hi"); โ โ
โ โ println!("{}", result); โ OK โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Lifetime annotation | 'a, a name for a region of code |
| Elision rule 1 | Each input reference gets its own lifetime |
| Elision rule 2 | One input lifetime โ assigned to the output |
| Elision rule 3 | &self lifetime โ assigned to the output |
| Annotation required | Multiple inputs, returned reference |
| Struct lifetime | struct S<'a> { f: &'a T } |
| Impl lifetime | impl<'a> S<'a> { ... } |
'static | The lifetime of the entire program |
| Lifetime bound | T: 'a |
| Multiple lifetimes | Separate parameters for unrelated borrows |
Key takeaways:
- A lifetime annotation is a name for a region of code, not a runtime value. The
'asyntax names the region, and the compiler uses it to reason about how long a reference is valid. - The elision rules infer the lifetimes for most functions. The rules are: one lifetime per input, one input lifetime assigned to the output, and
&selfassigned to the output. The explicit annotation is only needed when the elision is ambiguous. - The annotation is required when a function takes multiple references and returns a reference. The compiler cannot know which input the output borrows from, so the developer must write the annotation to resolve the ambiguity.
- A struct that holds a reference must declare a lifetime parameter. The lifetime is part of the struct’s type, and it bounds the struct’s lifetime to the referenced data.
- An
implblock on a struct with a lifetime must declare the lifetime parameter. The&selfparameter has the struct’s lifetime, and the elision rules assign it to the output. - A method that returns a reference from a parameter other than
&selfneeds an annotation. The annotation says which lifetime the output borrows from, and it allows the caller to pass arguments with different lifetimes. - The
'staticlifetime is the lifetime of the entire program. String literals have the'staticlifetime. AT: 'staticbound means that the type contains no references with a shorter lifetime. - Separate lifetimes are less restrictive than a shared lifetime. A function that returns a reference from one input should use a separate lifetime for the other input, so the caller is not required to make both inputs live equally long.
Remember: Lifetime annotations are the syntax for naming the regions of code over which references are valid. The elision rules infer the lifetimes for most functions, and the explicit annotation is only needed when the elision is ambiguous. A struct that borrows its data declares a lifetime parameter, and an impl block on the struct declares the same parameter. The 'static lifetime is the lifetime of the entire program, and a T: 'static bound means that the type contains no shorter-lived references. The result is a system where the compiler verifies that every reference is valid, and the developer only writes the annotations that the elision rules cannot infer.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!