Course contentsShow
← Rust
Lesson 381 of 1,9104. Lifetimes and Advanced OwnershipFree lesson

Lifetime Polymorphism

Abstract over lifetimes to write generic code that works across multiple contexts.

In this lesson, you will learn how to write functions that accept references with any lifetime, allowing your code to remain flexible across different contexts.

The Problem of Rigid Lifetimes

When you write a function like fn process(s: &str), the compiler is happy because of lifetime elision. However, when you start building complex structures—like structs that store references or traits that involve multiple borrowed objects—the compiler often demands explicit lifetime annotations. Sometimes, you want to write a function that works regardless of whether the references come from a short-lived stack frame or a long-lived global constant.

Understanding Lifetime Polymorphism

Lifetime polymorphism is the ability to write a generic function or trait implementation that doesn't care about the specific length of the lifetime provided, as long as it satisfies the required constraints. This is often achieved using the for<'a> syntax, which acts as a "for all" quantifier.

Consider a scenario where you want to accept a closure that can process a reference with any lifetime:

fn execute_with_ref<F>(callback: F) 
where 
    F: for<'a> Fn(&'a str) 
{
    let data = String::from("Hello, World!");
    callback(&data);
}

By using for<'a> Fn(&'a str), you are telling the compiler: "This function accepts any closure that can handle a string reference, no matter what its lifetime is." Without the for<'a> bound, the compiler might try to tie the lifetime of the closure's input to the specific lifetime of the function's internal scope, leading to borrow checker errors when you try to pass it data that lives longer or shorter than expected.

Why This Matters

This approach is essential for high-order functions and trait bounds. It allows your abstractions to be "lifetime agnostic," meaning they won't break just because you decide to move your data between different scopes. By abstracting over lifetimes, you decouple your business logic from the specific memory management of the data being passed, creating truly reusable Rust code.

Key Takeaway: Lifetime polymorphism allows you to write generic code that works for any lifetime by using the for<'a> quantifier, ensuring your functions are not overly restricted by the specific durations of the references they receive.