Rust: Rust Lifetimes: Annotating the Validity of References

Last updated: 2026-08-26

Lifetime is the mechanism Rust uses to ensure that "references never hang"—it assigns an "expiration date" to each reference.

You're Actually Already Using Lifetimes—All the code in Lessons 9 and 10 has lifetimes; the compiler just "guessed" them for you. This lesson teaches you when you need to manually specify lifetimes.


1. What You'll Learn



2. Conceptual Diagrams

100%
flowchart LR
    subgraph "Function Signature fn longest<'a>"
        X["x: &'a str"] --> RET["Return Value: &'a str"]
        Y["y: &'a str"] --> RET
    end
    NOTE["'a = the shorter of x and y's lifetimes"] -.-> RET


3. A Story About Chasing a Mouse

(1) Frustration: The mouse ran away, but I’m still watching

Tom is a biologist who is using a tracker to record the location of mice:

TEXT 📖 Display only
The tracker locks onto the mouse A → Mouse A ran into the cave → The tracker is still pointing toward the entrance.

"It would be great if the tracker had a label that said, 'Valid until Mouse A comes out of its hole'..."

(2) Solutions for the Rust Lifecycle

RUST
// Life Cycle Annotation 'a: Tell the compiler "the validity period of the return value does not exceed that of parameter x's validity period"
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let string1 = String::from("Long Strings");
    let result;

    {
        let string2 = String::from("short");
        result = longest(&string1, &string2);  // ✅ string2 and string1 are both still alive
        println!("The longer one is: {}", result);
    }  // string2 Destroyed here

    // println!("{}", result);  // ❌ If you print here, result references string2 which is destroyed
}  // string1 Destroyed here

Lifetimes are like "expiration date" labels attached to trackers. The compiler uses these labels to determine whether a reference is still within its lifetime. In this example, the lifetime of result cannot exceed that of string2.



4. How the Lifecycle Works

(1) Every reference has a lifecycle

RUST
fn main() {
    let x: i32 = 10;           // x Its life cycle begins here
    let r: &i32 = &x;          // r Its life cycle begins here
    println!("r: {}", r);       // Usage r
}                               // r whose lifecycle ends first, then x ends

(2) Why is it necessary to annotate lifecycles?

When a function returns a reference, the compiler needs to know which parameter the reference comes from:

100%
graph TB
    A[Functions Return References] --> B{Which parameter does the return value point to?}
    B --> C[Pointer Parameters1 → The Lifecycle of Return Values ≤ Parameters1]
    B --> D[Pointer Parameters2 → The Lifecycle of Return Values ≤ Parameters2]
    B --> E[Point to one of the two → Must be labeled: Find the intersection]
    C --> F[No annotation required (the compiler can infer)]
    D --> F
    E --> G[Manual annotation is required 'a]
Scenario Example Does it require annotation?
Single Input Reference fn first(x: &str) -> &str Not required (omission rule)
Multiple input references fn longest(x: &str, y: &str) -> &str Required (the compiler doesn't know which one to choose)
Does not return a reference fn len(x: &str) -> usize Not required
Structures containing references struct S<'a> { r: &'a i32 } Required

(3) Quick Reference for Life Cycle Annotation Locations

Location Syntax Description
Function Definition fn foo<'a>(x: &'a str) -> &'a str Parameters and Return Values
Structure Definition struct S<'a> { r: &'a str } A structure cannot outlive its reference
impl block impl<'a> S<'a> Declaring the Lifecycle When Implementing a Method
trait constraint where T: 'a Constraint types cannot contain short-lived references
Static annotation &'static str Valid throughout the entire program's execution


5. Lifecycle Annotation

(1) Syntax

RUST
// 'a is the name of the lifecycle parameter (using 'a, 'b, 'c)
// Pronunciation: Regarding the lifecycle 'a, both x and y need to be alive for at least 'a

fn function<'a>(x: &'a str, y: &'a str) -> &'a str {
    // The lifecycle of return values = the shorter of x and y
}

(2) Lifecycle Omission Rules

The compiler can automatically infer lifetimes (without you having to write them) in the following three cases:

Rule Meaning
Each input reference has its own lifecycle fn foo(x: &str) automatically assigns a lifecycle to x
Only one input reference The lifetime of the output reference = the lifetime of the input reference
Multiple inputs, one of which is &self or &mut self The lifetime of the output reference is &self
RUST
// No annotation required: there is only one input reference
fn first_word(s: &str) -> &str { &s[..] }

// Needs annotation: two input references
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}


6. Lifecycle Examples

▶ Example 1: Why Do We Need a Lifecycle? (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
The longer one is: <result>
<result>
RUST
// ============================================
// What happens if there are no lifecycle annotations?
// ============================================

// This function has two input references, the return value could be any one of these
// ❌ If not annotated, a compilation error will occur: expected lifetime parameter
// fn longest_wrong(x: &str, y: &str) -> &str {
//     if x.len() > y.len() { x } else { y }
// }

// ✅ Correct Version: Lifecycle Annotation
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("hello");
    let result;

    {
        let s2 = String::from("hi");
        result = longest(&s1, &s2);  // result lifecycle = s2 lifecycle (the shorter one)
        println!("The longer one is: {}", result);
    }  // s2 destroyed, result invalid

    // println!("{}", result);  // ❌ The compiler will prevent: result's lifecycle has ended
}

Output:

TEXT 📖 Display only
result: <result>
Remains valid even after leaving the inner layer: <result>




The lifetime 'a is set to the shorter of x and y. If the lifetime of the return value were set to the longer one, it might still be in use after the shorter one has been destroyed—which is exactly what the compiler is trying to prevent.


▶ Example 2: Parameters for Different Life Cycles (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
result: <result>
Remains valid even after leaving the inner layer: <result>
RUST
// ============================================
// The two parameters have different lifecycles.
// ============================================

// Two Lifecycles: 'a corresponds to x, 'b corresponds to y
// The return value depends only on x, so use 'a
fn choose_first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x  // Return only x, so the lifecycle only needs 'a
}

fn main() {
    let x = String::from("Live to be a hundred years old");
    let result;
    {
        let y = String::from("Short-lived ghost");
        result = choose_first(&x, &y);  // ✅ result Depends solely on x
        println!("result: {}", result);
    }  // y destroyed, but result does not depend on y, so no problem

    println!("Remains valid even after leaving the inner layer: {}", result);  // ✅ x Still Alive
}

Output:

TEXT 📖 Display only
result: Live to be a hundred years old
Remains valid even after leaving the inner layer: Live to be a hundred years old

Output:

TEXT 📖 Display only
Announcement: <announcement>

When the return value depends solely on a particular parameter, you only need to annotate that parameter’s lifetime. Even if the choose_first parameter in y is short-lived, it does not affect the result—because the return value does not use any data from y at all.


▶ Example 3: Lifecycle in Structures (Difficulty ⭐⭐⭐⭐)

Output:

TEXT 📖 Display only
Announcement: <announcement>
Excerpt: <excerpt.content>
Length: <excerpt.length()>
Announcement of Results: <excerpt.announce("Welcome to this article")>
RUST
// ============================================
// Storing references in a structure -- the lifecycle must be specified.
// ============================================

struct Excerpt<'a> {
    content: &'a str,  // Structures Borrow External Strings
}

impl<'a> Excerpt<'a> {
    fn length(&self) -> usize {
        self.content.len()
    }

    fn announce(&self, announcement: &str) -> &str {
        println!("Announcement: {}", announcement);
        self.content  // Returning a Reference to the Interior of a Structure
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Can't find the period");

    let excerpt = Excerpt {
        content: &first_sentence,
    };

    println!("Excerpt: {}", excerpt.content);
    println!("Length: {}", excerpt.length());
    println!("Announcement of Results: {}", excerpt.announce("Welcome to this article"));
}  // excerpt must be destroyed before novel (content depends on novel)

Output:

TEXT 📖 Display only
Excerpt: Call me Ishmael
Length: 15
Announcement of Results: Call me Ishmael

When a structure contains references, you must specify a lifetime parameter <'a> on the structure name. This tells the compiler that the lifetime of a structure instance cannot exceed the lifetime of the data it references.


▶ Example 4: 'static' Lifecycle (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
I exist throughout the entire lifecycle of a program
<std::ptr::from_ref(val)>
RUST
// ============================================
// 'static: the reference remains valid throughout the program's execution.
// ============================================

fn main() {
    // A string literal is 'static
    let s: &'static str = "I exist throughout the entire lifecycle of a program";
    println!("{}", s);

    // 'static Constraints: T must not contain any non-static references
    fn print_static<T: 'static>(val: &T) {
        println!("{:?}", std::ptr::from_ref(val));
    }

    let num: i32 = 42;
    print_static(&num);  // ✅ i32 excludes references, complies with 'static

    // let msg = String::from("temp");
    // let r = &msg;
    // print_static(&r);  // ❌ r references non-'static msg
}

Output:

TEXT 📖 Display only
I exist throughout the entire lifecycle of a program
0x...(Memory Address)

Output:

TEXT 📖 Display only
=== Text Analysis ===

'static is the longest lifetime in Rust—data exists from the moment the program starts until it ends. String literals have a 'static lifetime. Please note: 'static should be interpreted as "this reference is valid forever," rather than "this data survives until the end."


▶ Example 5: Comprehensive Exercise—Text Analyzer (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
=== Text Analysis ===
<analyzer.summary()>

First 3 words: <top3>
Includes 'Rust' the word: <rust_mentions>
RUST
// ============================================
// Life Cycle in Practice: Zero-Copy Text Analysis
// ============================================

struct TextAnalyzer<'a> {
    text: &'a str,
}

impl<'a> TextAnalyzer<'a> {
    fn new(text: &'a str) -> Self {
        TextAnalyzer { text }
    }

    fn word_count(&self) -> usize {
        self.text.split_whitespace().count()
    }

    fn longest_word(&self) -> &'a str {
        self.text
            .split_whitespace()
            .max_by_key(|w| w.len())
            .unwrap_or("")
    }

    fn first_n_words(&self, n: usize) -> Vec<&'a str> {
        self.text.split_whitespace().take(n).collect()
    }

    fn line_count(&self) -> usize {
        self.text.lines().count()
    }

    fn char_count(&self) -> usize {
        self.text.chars().count()
    }

    fn summary(&self) -> String {
        format!(
            "Character: {}, Words: {}, Number of lines: {}, Longest Word: '{}'",
            self.char_count(),
            self.word_count(),
            self.line_count(),
            self.longest_word()
        )
    }
}

fn highlight_word<'a>(text: &'a str, word: &str) -> Vec<&'a str> {
    text.split_whitespace()
        .filter(|w| w.contains(word))
        .collect()
}

fn main() {
    let article = "Rust is a systems programming language \
that runs blazingly fast and prevents segfaults. \
Rust guarantees memory safety and thread safety.";

    let analyzer = TextAnalyzer::new(article);
    println!("=== Text Analysis ===");
    println!("{}", analyzer.summary());

    let top3 = analyzer.first_n_words(3);
    println!("\nFirst 3 words: {:?}", top3);

    let rust_mentions = highlight_word(article, "Rust");
    println!("Includes 'Rust' the word: {:?}", rust_mentions);

    let paragraph = "First line.\nSecond line.\nThird line.";
    let p_analyzer = TextAnalyzer::new(paragraph);
    println!("\nParagraph Analysis: {}", p_analyzer.summary());
}

Output:

TEXT 📖 Display only
=== Text Analysis ===
Character: 124, Words: 18, Number of lines: 1, Longest Word: 'blazingly'

First 3 words: ["Rust", "is", "a"]
Includes 'Rust' the word: ["Rust", "Rust"]

Paragraph Analysis: Character: 39, Words: 6, Number of lines: 3, Longest Word: 'Second'

TextAnalyzer Holds a reference to the original text throughout its lifecycle <'a>—all analysis methods are zero-copy. longest_word The returned &'a str points to word slices in the original text and does not result in any memory allocation.


❓ FAQ

Q Is a lifetime a runtime concept?
A No! A lifetime is entirely a compile-time concept.
Q What does the "a" in "a" mean?
A It's just a name; it's customary to use a, b, c, and so on.
Q Why aren’t lifetime annotations visible in most Rust code?
A Because of the lifetime omission rule, which lets the compiler infer them for us.
Q What is the relationship between the lifetime of the input reference 'a' and the return value 'a'?
A They take the intersection.
Q When do I need to define a lifecycle myself?
A There are only three cases: when a function takes multiple input references and returns a reference, when a struct contains a reference, and when implementing a trait that constrains associated types.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Read a piece of existing Rust code (such as the example from Lesson 9) and identify which functions avoid manual annotation thanks to the lifetime elision rule.
  2. Difficulty ⭐⭐⭐: Write a function fn shortest<'a>(x: &'a str, y: &'a str) -> &'a str that returns the shorter of two strings. In main, create variables with different lifetimes to call it.
  3. Difficulty ⭐⭐⭐⭐: Define a struct Book<'a> that contains a field title of type &'a str. Write a impl block that implements a method fn first_word(&self) -> &str returning the first word of the title. Demonstrate its use in main.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏