Error E0310

"the parameter type T may not live long enough" — How to Fix

Fix Rust Error E0310 by adding explicit lifetime parameters to ensure returned references do not outlive their input data.

The generic type that vanishes too soon

You're building a generic container. You want it to hold a reference to any type. You write struct Container<T> { item: &T }. You hit compile. The compiler throws E0310: the parameter type T may not live long enough. You look at the code. The reference is right there. Why does the compiler think T is disappearing?

Rust treats generic types as complete unknowns until they are instantiated. When you write T, you are telling the compiler this code must work for every possible type. That includes types that exist for only a fraction of a second. If T is a temporary value, a reference to T becomes a dangling pointer the moment the temporary is dropped. The compiler refuses to generate code that could produce a dangling pointer. E0310 is the compiler demanding proof that T survives long enough for the reference to be safe.

Think of T as a rental car you haven't seen yet. You want to keep the keys. You can't promise you'll return the car if you don't know when the rental expires. The rental company requires a contract stating the car stays in your possession for a specific duration. Without that contract, they won't hand over the keys. The compiler is the rental company. T is the car. The lifetime bound is the contract.

The minimal trap

Here is the code that triggers the error. It looks harmless, but it hides a lifetime hole.

// This struct definition fails to compile.
struct ReferenceHolder<T> {
    // E0310: the parameter type T may not live long enough
    // The compiler sees &T and needs a lifetime to track it.
    // It also needs proof that T outlives that reference.
    target: &T,
}

The compiler rejects this with E0310. The error message points to T. The compiler is saying: "I don't know what T is. T could be a temporary. T could be a reference to something that dies instantly. If I allow this struct, I might generate code that stores a dangling reference. Prove to me that T lives long enough."

The fix requires two changes. First, the struct needs a lifetime parameter to name the duration of the reference. Second, T needs a bound stating it lives at least that long.

// The fixed version.
// 'a names the lifetime of the reference.
// T: 'a proves that T outlives 'a.
struct ReferenceHolder<'a, T>
where
    T: 'a,
{
    target: &'a T,
}

Now the compiler can generate safe code. If someone tries to use ReferenceHolder with a temporary T, the compiler catches the error at the call site. The definition is sound because the contract is explicit.

How the compiler checks the contract

When you add T: 'a, you are adding a constraint to the generic type. This constraint propagates to every use of the struct. The compiler performs a check at instantiation time.

Suppose you try to create a ReferenceHolder with a string slice.

fn main() {
    let text = String::from("Hello");
    
    // This works. 'text lives for the scope of main.
    // T is String. String: 'a is satisfied because String is owned.
    let holder = ReferenceHolder {
        target: &text,
    };
    
    // This fails. The temporary string is dropped at the end of the line.
    // T is String, but the reference is to a temporary.
    // The compiler rejects this with E0597: borrowed value does not live long enough.
    // let bad_holder = ReferenceHolder {
    //     target: &String::from("Temporary"),
    // };
}

The bound T: 'a does not just check the type T. It checks the data you pass. If T contains references, those references must also outlive 'a. This is recursive. If T is &'b str, then T: 'a implies 'b: 'a. The reference inside T must live at least as long as 'a. The compiler tracks this chain automatically. You just need to provide the entry point with the bound.

Realistic scenario: a generic view

Generic structs with references appear often in UI frameworks, parsers, and caches. Consider a view struct that displays data without owning it.

use std::fmt::Display;

// A view that holds a reference to any displayable model.
// Without the bound, this triggers E0310.
struct View<'a, T>
where
    T: Display + 'a,
{
    // The model reference must live for 'a.
    // T must also live for 'a.
    model: &'a T,
    label: &'a str,
}

impl<'a, T> View<'a, T>
where
    T: Display + 'a,
{
    fn render(&self) -> String {
        // SAFETY: Not unsafe, but documenting why we use Display.
        // T implements Display, so we can format it safely.
        format!("{}: {}", self.label, self.model)
    }
}

fn main() {
    let count = 42;
    
    // Create a view pointing to the count.
    // T is i32. i32: 'a is satisfied.
    let view = View {
        model: &count,
        label: "Counter",
    };
    
    println!("{}", view.render());
}

This pattern scales. The View struct works with any type that implements Display. The bound T: 'a ensures the model doesn't vanish while the view exists. The Display bound ensures the view can render the model. Both bounds are necessary. Missing Display triggers E0277. Missing 'a triggers E0310.

Pitfalls and compiler errors

Adding lifetime bounds solves E0310, but it introduces new constraints. Misusing these constraints leads to other errors.

Reaching for T: 'static too early kills flexibility. The 'static lifetime means the data lives for the entire program. If you bound T with 'static, you cannot pass a borrowed string slice. You must pass an owned String or a string literal. This forces allocations where none were needed.

// This struct requires T to be 'static.
struct StaticHolder<T>
where
    T: 'static,
{
    data: T,
}

fn main() {
    let owned = String::from("Owned");
    
    // This works. String is owned, so it is 'static relative to its scope?
    // No. String is not 'static. It is dropped when scope ends.
    // StaticHolder<String> works only if you move the String into a context
    // that keeps it alive, or if you use 'static incorrectly.
    // Actually, T: 'static means T contains no non-static references.
    // String contains no references, so String: 'static is true.
    let _holder = StaticHolder { data: owned };
    
    let slice = "Slice";
    // This fails. &str is not 'static unless it's a literal.
    // E0308: mismatched types. The compiler expects a 'static type.
    // let _bad = StaticHolder { data: slice };
}

The error E0308 mismatched types often follows when you try to pass a temporary to a 'static bound. The compiler cannot coerce a temporary into a 'static reference. Another trap is forgetting PhantomData. If you add a lifetime parameter but no field uses it, the compiler warns about unused lifetimes. Add std::marker::PhantomData<&'a T> to consume the lifetime without taking memory. This is common when you have a lifetime in a bound but the field owns the data.

Convention aside: The community convention is to use the smallest bound that works. Prefer T: 'a over T: 'static. If you need 'static, document why. Usually it's for thread safety or global storage. Also, keep lifetime parameters on the struct, not the function, when the lifetime belongs to the data structure. This keeps the API clean. When you see Rc::clone(&data) in the wild, notice the explicit form. It signals a shallow clone. Similarly, T: 'a signals a lifetime constraint. Write it explicitly. Don't rely on elision for generic bounds. Elision rules don't apply to bounds.

Decision: when to use bounds

Use T: 'a when your generic type holds a reference and you need to track how long that reference is valid. Use T: 'static when the generic type must contain no borrows, such as when storing values in a thread pool or a global cache. Use owned types like T instead of &T when you cannot guarantee the input lives long enough, shifting the lifetime burden to the caller. Use Cow<T> when you want to accept both owned and borrowed data without forcing the caller to allocate.

Lifetimes are not runtime overhead. They are compile-time contracts. When E0310 appears, the compiler is asking for a contract. Write the bound, prove the duration, and move on.

Where to go next