---
title: 'Lifetime Elision Rules'
source: 'https://academia.sh/en/courses/ownership/lifetime-elision-rules'
course: 'Ownership and the Memory Model'
language: en
updated: '2026-09-15T04:58:41+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Identifies the two signature forms lifetime elision covers'
    - 'Runs the explicit and implicit forms of the same signature to show they give the same behavior'
    - 'Shows with a rejection-acceptance pair that the annotation stays mandatory in a signature elision does not cover'
---

# Lifetime Elision Rules

Measures the rules the compiler uses to derive a lifetime annotation that is not written in a signature. The same signature is written and run in both explicit and implicit form, and both are shown to give the same behavior; a rejection-acceptance pair tests a signature elision does not cover, where the annotation stays mandatory.

The previous lesson measured when explicit annotation is required and what it expresses. But
most of the functions seen in the course's earlier lessons — ones that take a single input
and return a reference, or methods that operate through `self` — carried no explicit
annotation at all and still compiled. This lesson fills that gap: which rules the compiler
uses to derive the unwritten annotation, and what happens in signatures those rules do not
cover.

This question resembles the axis of the Go Fundamentals course — "what fills in for what is
not written" — but it is not the same thing: what was measured there was where an unwritten
type's information was completed from by the compiler; what is measured here is **where** a
reference's life ends — elision rules only lift the requirement to write this information
into the source, they do not change the information itself. The question the Go curriculum's
Concurrency course asked — "how does a running unit stop" — does not apply here either; this
lesson still measures a reference's validity range, not a unit of work stopping.

Elision itself is not a new source of information. The principle established in the previous
lesson still holds: the compiler never guesses, it only shortens the writing in cases where it
would **always reach the same conclusion**. When a signature form falls within elision's
scope, that means every signature of that form has exactly one correct reading; the path from
input to output carries no more than one possibility. This lesson measures where that scope
begins and where it ends.

This also has an important consequence for someone reading the source: if no annotation
appears in a signature, that can mean two different things. Either the function has no
reference relationship with the returned value at all, or the relationship exists but the
signature form falls into one of the two patterns elision covers. The difference between
these two is told apart by looking at the signature and checking the number of inputs and
whether `self` is present — the two measurements below make this check concrete.

## Three Rules, Two Conveniences

The relationship the compiler assumes when an annotation is not written in a signature is
called **elision**. Rust's language specification defines this assumption with three rules.
The first rule gives every input reference its own separate, unnamed life — this always holds
and produces no output on its own. The second rule, if the input has exactly one reference,
assigns that input's life to every elided output reference. The third rule, if `&self` or
`&mut self` is among the inputs, assigns `self`'s life to every elided output reference.

The second and third rules are, in practice, the ones that make an annotation unnecessary —
the first rule only names the input side, it ties the output to no input at all. For example,
in a function that takes two references and returns no reference at all, only the first rule
applies: each input gets its own separate life and the matter ends there, because there is no
output to elide. The second and third rules only come into play when there is an elided
output reference, and they determine which input that output is tied to.

The second rule's "exactly one input" condition looks at the **number of references** among
the inputs, not the number of inputs. If a function takes a number together with a reference,
even though the input list carries two items, only one input carries a life — the second rule
still applies. In the same way, if a function takes a single reference and returns a tuple
containing multiple references, every elided output part gets the life of that same single
input; the rule applies not to "a single output" but to "every elided output." The two
measurements below show these two conveniences separately: first a single-input function,
then a method that takes `&self`.

## Measurement: A Single-Input Signature

If a function takes only a single reference and returns a reference, the second rule comes
into play: the life of the single elided input is assigned to the elided output. Below, the
same function is written twice — once without an annotation, once with an explicit `'a` — and
both give the same result.

```rust
// elision_single_input.rs — single-input signature: same behavior even without the annotation.

struct Trace {
    name: String,
}

// Implicit: the compiler assigns the single input's life to the returned reference.
fn first_three_letters(trace: &Trace) -> &str {
    &trace.name[..3]
}

// Explicit: the same relationship written out by hand.
fn first_three_letters_explicit<'a>(trace: &'a Trace) -> &'a str {
    &trace.name[..3]
}

fn main() {
    let value = Trace { name: "scope-1".to_string() };
    println!("implicit -> {}", first_three_letters(&value));
    println!("explicit -> {}", first_three_letters_explicit(&value));
}
```

```
implicit -> sco
explicit -> sco
```

`first_three_letters` and `first_three_letters_explicit` carry the same body and give the
same output; the only difference between them is whether the annotation is visible in the
source. The compiler builds the same relationship for the first as it does for the second
while compiling it — and there is no other possibility, because the input is single: the
returned reference either comes from `trace` or does not exist at all.

**LI6.** Elision rules make an annotation unnecessary in cases where the compiler would
**always** reach the same conclusion. In a single-input signature there is no real ambiguity
about which input the returned reference will be tied to — there is already only one input.

This is seen more clearly next to the previous lesson's `primary_name` function:
`primary_name` could not be written without an annotation because it took two inputs, since
whether the returned reference came from `primary` or from `other` could not be inferred from
the source. `first_three_letters` takes a single input, so that same ambiguity never
arises — the elision rule makes no real choice here, it only spares the source from writing
down an option that was already the only one.

## Measurement: A Method That Takes `self`

The third rule ties the elided output to `self`'s life at all times, even if a method takes
`self` along with other references. This explains why many of the methods seen in the
course's earlier lessons carried no annotation at all.

```rust
// elision_self.rs — a method taking &self: the returned reference ties to self even without the annotation.

struct Trace {
    name: String,
}

impl Trace {
    // Implicit: self's life is assigned to the returned reference.
    fn name(&self) -> &str {
        &self.name
    }
}

struct TraceExplicit {
    name: String,
}

impl TraceExplicit {
    // Explicit: the same assignment written out by hand.
    fn name<'a>(&'a self) -> &'a str {
        &self.name
    }
}

fn main() {
    let value = Trace { name: "scope-2".to_string() };
    let explicit = TraceExplicit { name: "scope-2".to_string() };
    println!("implicit -> {}", value.name());
    println!("explicit -> {}", explicit.name());
}
```

```
implicit -> scope-2
explicit -> scope-2
```

`Trace::name` and `TraceExplicit::name` give the same behavior. What stands out here is that
the result would not change even if the method took another reference besides `self` — the
third rule looks only at `self`, it does not take the other inputs into account at all. This
is the same conclusion resting on a different foundation than the second rule: the compiler
assumes that in a method taking `self`, the returned reference is almost always part of
`self`, and this assumption was considered reliable enough to be turned into a rule.

Together, these two rules also explain why most of the methods seen in the course's earlier
lessons carried no annotation: methods that returned a reference to a type's own field took
`&self`, and the third rule came into play; free functions that read a value and returned a
derived reference were mostly single-input, and the second rule came into play. Where an
explicit annotation genuinely appeared, as in the previous lesson, was in signatures where
multiple inputs were present together without `self`.

**LI7.** Two different rules produce the same outcome in two different signature forms: in
single-input functions the input itself determines the output's life; in methods taking
`self`, `self` itself determines it. In both forms the signature is accepted even when no
annotation is ever written; what the two have in common is that there is only one place the
returned reference can go.

## The Order for Reading a Signature

Together, the two measurements above also give an order for determining, by looking at a
signature, whether an annotation is required. First, the number of references among the
inputs is counted: if there are no references, or no output reference to elide, the first
rule alone is enough and the matter is settled. If the input has exactly one reference, the
second rule comes into play and the output is tied to it — `first_three_letters` takes this
path. If the input has more than one reference, the next question is whether `self` is
present: if it is, the third rule comes into play and the output is tied to `self` —
`Trace::name` takes this path. If there is more than one reference and `self` is absent, none
of the three rules can reach a single conclusion, and an explicit annotation becomes
mandatory — `longer_name` falls into this last case.

This ordering is less about the compiler's own inner workings and more a useful shortcut for
a reader to quickly judge, by looking at a signature, whether to expect an annotation. Most of
the functions seen through the course are settled in the first two steps; the places where an
explicit annotation appears are exactly the signatures that fall into this ordering's last
step, the ones carrying more than one reference of equal weight.

This ordering itself requires no tool or checker — the three items can be memorized and
applied by hand to any signature. This repeats the same pattern as the earlier measurements
about lifetimes: the source, on its own, without being run, carries information that can be
read. The difference is that what is read here is not a reference's range but the decision of
whether that range is **written down** or not.

## Limiting Measurement: Elision Is a Convenience, Not a Rule

Elision rules are limited to three items, and there are signatures those three items do not
cover. The clearest example is a function that contains no `self` and takes more than one
reference: neither the second rule (no single input) nor the third rule (no `self`) comes into
play. In such a function there is no single conclusion the compiler can reach on its own about
which input the returned reference is tied to — the first rule gives every input its own
separate life and assigns none of them to the output.

The two-reference, `self`-free function seen in the previous lesson (`primary_name`) was in
exactly this situation and carried an explicit `'a` — there, the annotation built a narrow
promise tied to only one of the two inputs. The same boundary is tested here with a new
example, this time with a situation where both inputs can genuinely be returned: a function
that takes two `Trace` references and returns the slice of whichever name is longer is
rejected by the compiler if written without an annotation, because the compiler cannot
determine which input is returned. Depending on a runtime comparison, the body can return
either `x` or `y` — which shows that the inputs are of equal weight, with neither taking
priority over the other. The accepted form ties both to the same life with an explicit `'a`.

```rust
// non_elided.rs — a signature elision does not cover: it does not compile without the annotation, it compiles once written.

struct Trace {
    name: String,
}

fn longer_name<'a>(x: &'a Trace, y: &'a Trace) -> &'a str {
    if x.name.len() >= y.name.len() { &x.name } else { &y.name }
}

fn main() {
    let a = Trace { name: "short".to_string() };
    let b = Trace { name: "a-longer-name".to_string() };
    println!("{}", longer_name(&a, &b));
}
```

```
a-longer-name
```

The unannotated form of `longer_name` matches none of the three rules: there are two inputs,
and no `self`. The compiler rejects the signature because it cannot determine whether the
returned reference comes from `x` or from `y`; the accepted form applies the same solution
seen in the previous lesson's `other_name` function — tying both inputs to the same `'a`. The
run above works with this accepted form and compares the two names, returning the longer one.

The difference between `other_name` and `longer_name` is not in the grounds for acceptance,
but in **when** the return decision is settled: in `other_name`, which parameter would be
returned was fixed in the function's body; in `longer_name`, that decision is made fresh on
every call, by a runtime comparison. In both cases the annotation does the same job — it
covers every source that could possibly be returned in advance — because which branch will be
chosen is not known at compile time; the annotation has to cover every possible path, not
just the one actually taken.

**LI8.** Elision rules are a **convenience**, not a guarantee that the language can compile
every signature. When none of the three rules fits a signature, the annotation is mandatory
again; the rules only determine **when it is not needed**, not that it will never be needed.

This boundary rests on the same root as the principle established in the previous lesson: the
compiler only skips an annotation when the source yields exactly **one correct reading**. In
a function with two equally weighted inputs like `longer_name`, there is no single reading —
`x` and `y` are of the same type, in the same position, and either can be returned. None of
the three rules contains an assumption that would resolve this ambiguity, because any
assumption could reject some correct programs while accepting some incorrect ones. Rather than
take that risk, Rust rejects the code and leaves the decision to the developer; this shows why
the annotation stays mandatory exactly where it functions as a guarantee, not a convenience.

## Summary

- Rust's elision rules make writing a lifetime annotation unnecessary for some signature
  forms; two of these were measured in this course: single-input functions and methods that
  take `self`.
- In a single-input function, the elided input's life is assigned to the elided output; in a
  method taking `self`, this assignment is always made to `self`, regardless of the number of
  inputs.
- The explicit and implicit forms of the same signature gave exactly the same behavior in
  both measured cases; in both forms, the values ended **at scope end**.
- This lesson differs from the Go Fundamentals course's axis, which measured unwritten type
  information: what is measured here is where a reference's life ends, not where type
  information is completed from.
- In signatures the three rules do not cover (multiple inputs, no `self`), the annotation
  stays mandatory; a signature outside their coverage is rejected if written without one.
- Elision applies in cases where the compiler derives **exactly one correct reading** from a
  signature; when more than one equally weighted possibility exists, the compiler rejects the
  code rather than make an assumption.

## Next Step

This lesson showed which signatures elision covers and which it does not — the annotation
was unnecessary in the ones it covers, still mandatory in the ones it does not, and this
necessity was established between a function's parameters. But the same need can also arise
in a struct's own field: if a struct holds a reference, the struct itself cannot outlive the
data it refers to, and this condition also needs to be written with an annotation. The next
lesson measures this condition by comparing two designs that hold the same data, one by
moving it into a struct, the other by borrowing it.
