Skip to content
2024-10-28

004 - Lifetimes Without Fear

Lifetimes Without Fear

Why this matters

Lifetimes look intimidating, but they are mostly a way to make reference validity explicit. You rarely annotate everything—only where the compiler needs clarity.

Learning goals

By the end, you should be able to:

  • Explain what a lifetime annotation expresses.
  • Read and write simple lifetime-annotated functions.
  • Recognize when returning an owned value is simpler than returning a reference.

Mental model

A lifetime annotation does not extend a value’s life.

It only describes relationships between references so Rust can prove safety.

Python baseline

Python returns references under the hood freely, and object lifetimes are runtime-managed.

Rust requires proof that returned references stay valid.

Classic example

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}

Meaning: returned &str is valid as long as both inputs are valid, effectively the shorter overlap.

Runnable end-to-end example

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() {
        a
    } else {
        b
    }
}

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

fn main() {
    let s1 = String::from("rust");
    let s2 = String::from("pythonista");

    let chosen = longest(&s1, &s2);
    println!("longest = {}", chosen);

    let excerpt_source = String::from("memory mastery in rust");
    let first_word = excerpt_source.split_whitespace().next().unwrap();

    let snippet = Snippet { text: first_word };
    println!("snippet = {}", snippet.text);
}

When to avoid lifetime complexity

If references make signatures hard to use, prefer returning owned data:

fn best_title(a: &str, b: &str) -> String {
    if a.len() >= b.len() { a.to_string() } else { b.to_string() }
}

You pay an allocation, but gain simpler API boundaries.

Common pitfalls

  • Assuming lifetimes change runtime behavior.
  • Trying to return references to local temporaries.
  • Over-annotating where lifetime elision already works.

Quick practice

  1. Write fn first<'a>(items: &'a [i32]) -> Option<&'a i32>.
  2. Create a struct that holds two &str fields with one lifetime parameter.

Recap

Lifetimes are contracts about reference validity. Keep signatures simple, and prefer owned returns when APIs become too complex.

Next lesson: 005 - String, &str, and Allocation Patterns.

Join the Journey Ahead!

If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.

Other articles in the series