Skip to content
← All articles

Outlives bounds: 'a: 'b, T: 'a, and implied bounds

Once you write generic code that holds references, these bounds appear in every error message. Without the vocabulary they are noise; with it, `T: 'a` errors become readable. This is the formal grammar, and the prerequisite for everything above it.

Every error message in generic Rust that touches references contains one of these two shapes:

'a: 'b        T: 'a

Until you can read them, those messages are noise. Once you can, most of them say something simple and actionable. This is the grammar.

'a: 'b — one lifetime outlives another

'a: 'b       // read: "'a outlives 'b"

It means 'a lasts at least as long as 'b. In the region model: the set 'a contains the set 'b. The Reference states the consequence directly — a &'a () is valid anywhere a &'b () is valid.

Most people read this backwards on first contact, because the colon looks like a type ascription and “'a: 'b“ reads like “'a is a 'b“. Do not fight your intuition with willpower; draw two nested boxes, 'b inside 'a. The bigger box outlives the smaller one, and the picture will still be there in six months when the words have gone.

The immediate consequence is subtyping: when 'a: 'b, the type &'a T is a subtype of &'b T, so a &'a T coerces into any position expecting &'b T. This fires silently in every program you have ever written — it is why passing a long-lived reference into a short-lived parameter Just Works.

T: 'a — a type outlives a lifetime

T: 'a        // read: "every lifetime parameter of T outlives 'a"

Or, in the phrasing that actually helps: T contains no references that could go bad before 'a ends.”

i32: 'static             // holds. No lifetimes at all.
String: 'static          // holds. Owned.
&'static str: 'a         // holds for any 'a. 'static contains everything.
Vec<&'a ()>: 'static     // does NOT hold, unless 'a is 'static.

Note that T: 'a is a much weaker claim than &'a T, and confusing the two is one of the most common intermediate errors:

fn t_bound<T: 'static>(_t: T) {}
fn t_ref<T: 'static>(_t: &'static T) {}

fn main() {
    let s = String::from("x");
    t_bound(s);        // fine — String contains no borrowed data
    t_ref(&s);         // error[E0597]: `s` does not live long enough
}

t_bound asks about the contents of the type and is satisfied by any owned value. t_ref asks for a reference to something that lives for the whole program, which a local String is not. Same 'static token, two entirely different requirements — which is the subject of its own article, and worth re-reading if that pair surprised you.

💡Which of these bounds hold? Give a one-line reason for each. click to reveal
1. u8: 'static
2. &'a u8: 'static
3. &'static u8: 'a
4. Vec<String>: 'a
5. (i32, &'a str): 'static

1 holds. No lifetime parameters at all, so the condition is vacuously true. Every primitive and every fully-owned type qualifies.

2 does not (unless 'a happens to be 'static). The type contains a reference valid only over 'a, and 'static requires validity everywhere.

3 holds, for every 'a. 'static contains every region, so 'static: 'a is always true, and therefore so is the bound on the type.

4 holds, for every 'a. Vec<String> has no lifetime parameters. Note that this is true for 'a = 'static as well — owned collections are always 'static.

5 does not. A tuple’s bound is the conjunction of its fields’ bounds: i32: 'static holds but &'a str: 'static does not, so the tuple fails. This is the general rule — a composite type outlives 'a exactly when all of its components do.

If 3 tripped you, that is the containment picture doing its work: 'static is the largest region, so it outlives everything, including 'a.

Implied bounds

Here is a piece of good news that is rarely stated plainly: you usually do not have to write T: 'a at all. rustc infers lifetime bounds that are implied by the types you already wrote.

fn f<'a, T>(x: &'a T) -> &'a T { x }

A &'a T cannot exist unless T: 'a — the reference type itself requires it — so the compiler gives you the bound for free. Same for structs:

struct S<'a, T> { d: &'a T }      // T: 'a is implied

Writing it out is not an error, but rustc has a lint for it:

#![warn(explicit_outlives_requirements)]
pub struct S<'a, T: 'a> { d: &'a T }
warning: outlives requirements can be inferred
  |
2 | pub struct S<'a, T: 'a> { d: &'a T }
  |                   ^^^^ help: remove this bound

It is allow-by-default (so it will not fail your submissions on this site), but turning it on is a cheap way to keep generic signatures from accumulating noise.

When implied bounds are not enough

Implied bounds are deliberately limited, and here is the canonical case where you have to write one yourself — it is rustc --explain E0311‘s own example:

fn no_restriction<T>(x: &()) -> &() {
    with_restriction::<T>(x)
    // error[E0311]: the parameter type `T` may not live long enough
}

fn with_restriction<'a, T: 'a>(x: &'a ()) -> &'a () { x }

no_restriction‘s parameter is &(), not &T. The elided lifetime on that &() has nothing to do with T, so no bound is implied, and the call to with_restriction demands one. The fix is to name the lifetime and state the bound:

fn no_restriction<'a, T: 'a>(x: &'a ()) -> &'a () {
    with_restriction::<T>(x)
}

Change the signature to take &T instead of &() and it compiles with no bound at all, because now the bound is implied. That contrast is the clearest possible demonstration of what “implied” means: the bound comes from a type mentioning both T and 'a, not from the compiler being clever.

💡Only lifetime bounds are implied. Trait bounds never are. Why is that asymmetry there, and what does it mean for you day to day? click to reveal

Because the two kinds of bound come from different places.

A lifetime bound like T: 'a is implied by a type being well-formed. The type &'a T cannot even be written down unless T: 'a; the requirement is baked into the reference type’s definition. So if a signature mentions &'a T, the compiler already had to check T: 'a to accept the signature, and it may as well let the body rely on it.

A trait bound like T: Display is not implied by anything. No type is ill-formed for lacking a Display impl. There is nothing for the compiler to derive it from, and inferring it would mean guessing at your intent — and, worse, would make your API’s requirements depend on your implementation, so that changing a body silently broke callers.

Day to day: every T: Display, T: Clone, T: Send you need must be written on the signature, every time, even when the body obviously requires it. The error will be error[E0277]: ... doesn't implement Display pointing at the body, and the fix is always to add the bound to the signature. Meanwhile you can usually delete T: 'a and let the compiler work it out.

It is worth internalising as a pair, because the failure modes look similar in a terminal and the fixes are opposite: add the trait bound, remove the outlives bound.

Two more codes, and one that is dead

E0310 — “the parameter type T may not live long enough … must be valid for the static lifetime”. You get this when a generic parameter is stored somewhere that requires 'static (a Box<dyn Trait> field, a thread::spawn closure) without the bound being declared.

E0311 — the same idea, but the region involved is elided rather than named, as in the example above.

E0478 — “lifetime bound not satisfied”. This is the trait-declaration flavour: a supertrait requires one lifetime to outlive another and the type declaring it does not say so.

trait Wedding<'t>: 't {}
struct Prince<'kiss, 'snow> { child: Box<dyn Wedding<'kiss> + 'snow> }
// error[E0478]: lifetime bound not satisfied

The fix is to write the relation down: struct Prince<'kiss, 'snow: 'kiss>.

E0477 you may see cited in older material. Like E0759, it is retiredrustc --explain E0477 says “this error code is no longer emitted by the compiler”. If a Stack Overflow answer hinges on it, it predates your compiler.

The card

shape reads as means
'a: 'b “‘a outlives ‘b” the region 'a contains the region 'b
T: 'a “T outlives ‘a” T contains no reference that dies before 'a ends
&'a T requires T: 'a, and implies it for you

Plus: lifetime bounds are implied where the types force them; trait bounds are never implied; and explicit_outlives_requirements will tell you which ones to delete.