Why does Rust need explicit lifetimes? Explaining with examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why does Rust force you to specify lifetimes? And how does it simplify things?

\n\n

If you're learning Rust, sooner or later you encounter this mysterious syntax: 'a, 'b... And a natural question arises: "Why do I have to explicitly specify lifetimes? Other languages manage without them!"

\n\n

This question is one of the most popular on Stack Overflow (thousands of views). And it's no wonder: the concept of lifetimes in Rust seems complicated until you understand its essence. Let's figure it out without boring theory, using real-life examples.

\n\n

Short answer: safety without a garbage collector

\n\n

Rust guarantees memory safety at compile time. It needs to know exactly how long references live to prevent dangling references. In Java or Go, a garbage collector handles this. In C++, you manage memory yourself (and often make mistakes). Rust chose a third path: the compiler checks everything statically.

\n\n

But why can't the compiler figure it out on its own? It can — in 80% of cases. This is called lifetime elision (implicit inference rules). However, in complex situations, it needs your help.

\n\n

Rule #1: The compiler is not a mind reader

\n\n

Look at this function:

\n\n
fn longest(x: &str, y: &str) -> &str {\n    if x.len() > y.len() { x } else { y }\n}
\n\n

At first glance, it's logical. But Rust will give an error: "missing lifetime specifier". Why?

\n\n

Imagine you passed x — a reference to a string that lives for 5 seconds, and y — a reference to a string that lives for 1 second. The function could return either of them. What will be the lifetime of the returned reference? The compiler doesn't know!

\n\n

The solution is to explicitly say: "the returned reference will live as long as both input references":

\n\n
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n    if x.len() > y.len() { x } else { y }\n}
\n\n

The symbol 'a is a lifetime annotation. We are saying: "all three references must have the same minimum lifespan." Now the compiler can verify that the calling code doesn't destroy the data prematurely.

\n\n

Rule #2: Different references — different lifetimes

\n\n

A common beginner mistake is thinking that all 'a must be the same. In fact, you can specify different lifetimes:

\n\n
fn example<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {\n    x // Ok, returning a reference with lifetime 'a\n    // y // Error! y lives for 'b, but we promised 'a\n}
\n\n

Here we explicitly say: x and y can have different lifetimes. But we can only return the reference whose lifetime matches the one promised in the return type.

\n\n

Rule #3: Structs can also store references

\n\n

When you create a struct that contains references, lifetimes in Rust must be specified:

\n\n
struct Book<'a> {\n    title: &'a str,\n    author: &'a str,\n}\n\nfn main() {\n    let title = String::from("Rust Book");\n    let book;\n    {\n        let author = String::from("John Doe");\n        book = Book { title: &title, author: &author };\n    } // author is destroyed here!\n    println!("{}", book.title); // Error! book.author is dangling\n}
\n\n

The compiler will not let this code compile because book.author references an already deleted string. Explicit lifetimes in Rust allow catching such errors at compile time, not at runtime.

\n\n

Rule #4: Lifetime Elision — when you can omit writing

\n\n

Rust is smart enough to infer lifetimes on its own in simple cases. Here's when you can omit them:

\n\n
    \n
  • One input reference — the output lifetime equals the input

Blogs

Book Recommendations