Skip to content

Rust Lifetimes — Annotating References for Safety

DodaTech Updated 2026-06-21 6 min read

Rust lifetimes are annotations that tell the borrow checker how long references are valid, ensuring no reference outlives the data it points to without runtime overhead.

What You'll Learn

In this tutorial, you'll learn how Rust lifetime annotations work, lifetime elision rules, how to annotate lifetimes in functions and structs, the 'static lifetime, and how to debug common lifetime errors.

Why It Matters

Lifetimes are the part of Rust that most beginners struggle with. Understanding them is essential for writing flexible libraries, working with references in structs, and passing the borrow checker's scrutiny. Without lifetimes, Rust could not guarantee that references are always valid.

Real-World Use

Database connection pools use lifetime-annotated references to ensure connections are returned before the pool is dropped. Parser combinators use lifetimes to track input validity. Durga Antivirus Pro's pattern matcher uses lifetime annotations to guarantee that scan results referencing file data remain valid.

flowchart LR
    subgraph "Function Scope"
        A[Owner created] --> B[Reference created]
        B --> C[Reference used]
        A --> D[Owner dropped]
    end
    B -.->|lifetime 'a| A
    C -.->|must be before| D
    style A fill:#90EE90
    style D fill:#FFB6C1
â„šī¸ Info

Prerequisites: Rust Ownership and Borrowing & References are required.

What Are Lifetimes?

Every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most lifetimes are inferred, but sometimes you must annotate them explicitly.

fn main() {
    let x = 42;           // ----------+-- 'a
    let r = &x;           // --+-- 'b  |
    println!("r: {}", r); //   |       |
}                         // --+       |

The reference r has lifetime 'b which is contained within 'a (the lifetime of x). The borrow checker verifies that 'b <= 'a, meaning the reference does not outlive the data.

Lifetime Annotation Syntax

Lifetime annotations use an apostrophe followed by a name: 'a, 'b, 'static. They appear in angle brackets after function names.

// Explicit lifetime 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("short");
    let s2 = String::from("longer string");
    let result = longest(&s1, &s2);
    println!("Longest: {}", result);
}

Expected output:

Longest: longer string

Lifetime Elision Rules

Rust has three elision rules that let you omit lifetime annotations in common patterns:

// Elision rule: input lifetime becomes output lifetime
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[..i];
        }
    }
    &s[..]
}

fn main() {
    let text = String::from("Rust lifetimes explained");
    let word = first_word(&text);
    println!("First word: '{}'", word);
}

Expected output:

First word: 'Rust'

Lifetimes in Structs

When a struct holds references, you must annotate lifetimes.

struct Excerpt<'a> {
    content: &'a str,
    source: &'a str,
}

impl<'a> Excerpt<'a> {
    fn summary(&self) -> &str {
        if self.content.len() > 20 {
            &self.content[..20]
        } else {
            self.content
        }
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let excerpt = Excerpt {
        content: &novel[..20],
        source: &novel,
    };
    println!("Excerpt: {}", excerpt.summary());
}

Expected output:

Excerpt: Call me Ishmael. So

The 'static Lifetime

'static means the reference lives for the entire program duration. String literals have 'static lifetime.

fn static_ref() -> &'static str {
    "This string lives forever" // string literals are 'static
}

fn main() {
    let s: &'static str = static_ref();
    println!("{}", s);

    // A 'static variable
    static RUST_LANG: &str = "Rust";
    println!("Language: {}", RUST_LANG);
}

Expected output:

This string lives forever
Language: Rust

Lifetime Bounds in Generics

Lifetime bounds constrain generic types to have at least a certain lifetime.

struct Container<'a, T: 'a> {
    value: &'a T,
}

fn print_value<'a, T: std::fmt::Display + 'a>(c: &Container<'a, T>) {
    println!("Value: {}", c.value);
}

fn main() {
    let num = 100;
    let c = Container { value: &num };
    print_value(&c);
}

Expected output:

Value: 100

Lifetime and Security Implications

Lifetime annotations are a compile-time security feature. They prevent:

  • Use-after-free: References cannot outlive their data
  • Dangling pointers: The compiler verifies all references point to valid memory
  • Iterator invalidation: Collection references are invalidated when the collection is mutated

These protections are critical for security software. Durga Antivirus Pro's scanning engine processes untrusted file data, and Rust's lifetimes ensure that scan results referencing internal file buffers never become dangling.

Common Mistakes

1. Omitting Lifetime Annotations When Required

Functions returning references from parameters need lifetime annotations. The compiler tells you exactly what to add.

2. Over-constraining with 'static

Using 'static for references that do not need to live that long. Prefer shorter, specific lifetimes.

3. Lifetime Annotations on Struct Methods Without impl Block

Struct methods need lifetime parameters declared on both the struct and the impl block.

4. Trying to Return a Reference to a Local Variable

A reference to a function-local variable cannot outlive the function. Return an owned type instead.

5. Ignoring Lifetime Elision

Most functions do not need explicit lifetimes thanks to elision rules. Add annotations only when the compiler requires them.

Practice Questions

1. What is a lifetime in Rust? A lifetime is the scope for which a reference is valid. Lifetimes ensure references do not outlive the data they point to, preventing dangling pointers.

2. What are the three lifetime elision rules? (1) Each input elided lifetime gets a distinct lifetime. (2) If there is exactly one input lifetime, it is assigned to all elided output lifetimes. (3) If &self or &mut self, its lifetime is assigned to all elided output lifetimes.

3. What does 'static mean? 'static means the reference is valid for the entire program duration. String literals and const values have 'static lifetime.

4. When do you need explicit lifetime annotations? When a function returns a reference and has multiple reference inputs, or when a struct holds references.

5. Challenge: Write a Parser struct that holds a &'a str and has a method that returns substrings. Ensure the lifetimes work correctly.

Mini Project: Lifetimed Log Parser

#[derive(Debug)]
struct LogEntry<'a> {
    level: &'a str,
    message: &'a str,
}

struct LogParser<'a> {
    data: &'a str,
}

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

    fn parse_next(&mut self) -> Option<LogEntry<'a>> {
        let rest = self.data.trim_start();
        if rest.is_empty() {
            return None;
        }
        let line_end = rest.find('\n').unwrap_or(rest.len());
        let line = &rest[..line_end];
        self.data = &rest[line_end..];

        if let Some(colon_pos) = line.find(':') {
            Some(LogEntry {
                level: &line[..colon_pos],
                message: &line[colon_pos + 1..].trim(),
            })
        } else {
            None
        }
    }
}

fn main() {
    let log = "INFO: System started\nERROR: Connection failed\nWARN: Retrying";
    let mut parser = LogParser::new(log);
    while let Some(entry) = parser.parse_next() {
        println!("[{:?}] {}", entry, entry.message);
    }
}

FAQ

What is the difference between lifetimes and references?

A reference is a pointer type. A lifetime is a compile-time annotation that describes how long the reference is valid. Every reference has a lifetime, but most lifetimes are inferred.

Can lifetimes cause runtime overhead?

No. Lifetime annotations are erased at compile time. They exist only for the borrow checker and have zero runtime cost.

How do I fix a lifetime mismatch error?

Check that all referenced data lives long enough. Ensure the output lifetime is connected to the input lifetimes. Add or adjust lifetime annotations as the compiler suggests.

Borrowing & References
Structs, Enums & Pattern Matching
Traits & Generics

What's Next

Now master Structs, Enums & Pattern Matching for data modeling, then explore Traits & Generics for polymorphism.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro