---
title: "17.2 Lifetime annotations in functions"
description: "The 'a syntax on a function that returns a reference, the longest example, and the crucial point that an annotation describes a relationship, it never extends a lifetime."
url: "https://learnrust.net/chapter-17/lifetime-annotations-in-functions/"
last_updated: "2026-06-13"
---

# 17.2 Lifetime annotations in functions

The first place you must write a lifetime by hand is a function that returns a reference borrowed from its inputs. This lesson introduces the `'a` syntax on the classic `longest` function and, more importantly, makes clear what the annotation actually means, because the single most common misunderstanding is thinking lifetimes *change* how long things live. They don't. They describe.

## A function that returns a reference

Here's a function that returns the longer of two string slices:

```rust
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}
```

This won't compile, and the reason is a real ambiguity. The function returns a reference, but *which* of its inputs does the returned reference borrow from? Sometimes `x`, sometimes `y`, decided at runtime by the lengths. The compiler needs to know the relationship between the inputs' lifetimes and the output's lifetime to guarantee the returned reference stays valid, and it can't infer it here:

```
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++
```

That `help` text is the clearest one-sentence statement of the whole problem: "the signature does not say whether it is borrowed from `x` or `y`." E0106 again, the same error as the struct case in lesson [10.8](https://learnrust.net/chapter-10/ownership-in-structs/), and the suggested fix is exactly what we'll write.

## The 'a syntax

A lifetime parameter looks like a generic type parameter (chapter 15) but starts with an apostrophe and is conventionally named `'a`. You declare it in angle brackets after the function name, then attach it to the references:

```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let s2 = String::from("short");
    println!("{}", longest(&s1, &s2));
}
```

```
long string
```

Read `<'a>` as "for some lifetime `'a`," and read the rest as a sentence: `x: &'a str` and `y: &'a str` both borrow for the same lifetime `'a`, and `-> &'a str` returns a reference that also lasts for `'a`. Together they tell the compiler: "the returned reference is valid for as long as *both* inputs are valid." That's the relationship the compiler needed. Now it can check at each call that the returned reference isn't used past the point where either input is still alive, exactly the dangling-reference protection from lesson [9.5](https://learnrust.net/chapter-9/dangling-references/), made expressible.

## The crucial point: it describes, never extends

Here is the misunderstanding to kill now. Writing `'a` does **not** make any value live longer. It is not a command; it's a *description*. The annotation tells the compiler about a relationship that *already exists* among the references, so the compiler can check it. It cannot create lifetime out of nothing.

You can prove this to yourself: if `longest` truly extended lifetimes, this would compile, but it doesn't:

```rust
fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("short");
        result = longest(&s1, &s2);
    }                                  // s2 dropped here
    println!("{result}");              // refused
}
```

```
error[E0597]: `s2` does not live long enough
 --> src/main.rs:6:31
  |
5 |         let s2 = String::from("short");
  |             -- binding `s2` declared here
6 |         result = longest(&s1, &s2);
  |                               ^^^ borrowed value does not live long enough
7 |     }
  |     - `s2` dropped here while still borrowed
8 |     println!("{result}");
  |                ------ borrow later used here
```

Because `longest` says the output lives only as long as *both* inputs, and `s2` dies at the inner brace, `result` can't be used afterward. The annotation didn't keep `s2` alive; it described the constraint, and the constraint says `result` is only valid while both inputs are. Lifetimes are a way of *stating* the rules so the compiler can enforce them, never a way to bend them.

> **Key insight**
>
> A lifetime annotation is a promise *about* references, not a power *over* them. `<'a>(x: &'a str, y: &'a str) -> &'a str` promises "the output borrows from these inputs and is valid only as long as they are." The compiler verifies the promise holds; it never grants extra lifetime to satisfy it. If you ever think "I'll add `'a` to make this live longer," stop, that's not what it does. The fix for "doesn't live long enough" is to restructure so the data genuinely lives long enough (or to own it), not to annotate harder.

## When you don't need it

Not every reference-returning function needs an annotation. If a function returns a reference borrowed from exactly one input, the compiler infers the relationship (that's elision, lesson [17.4](https://learnrust.net/chapter-17/elision-rules/)). `longest` needs the annotation precisely because it has *two* inputs and the output could come from either. A function like `fn first_word(s: &str) -> &str` (your friend from lesson [9.7](https://learnrust.net/chapter-9/string-slices/)) needs no annotation, because there's only one input the output could borrow from, so there's no ambiguity to resolve.

## Quiz time

**Question #1**

Why does `fn longest(x: &str, y: &str) -> &str` fail to compile without a lifetime annotation?

<details class="solution">
<summary>Show solution</summary>

Because it returns a reference, but the signature doesn't say whether that reference borrows from `x` or `y` (it could be either, decided at runtime). The compiler needs to know the relationship between the inputs' and output's lifetimes to guarantee the returned reference stays valid, and it can't infer it with two candidate inputs. That's E0106, missing lifetime specifier.

</details>

**Question #2**

What does `fn longest<'a>(x: &'a str, y: &'a str) -> &'a str` tell the compiler?

<details class="solution">
<summary>Show solution</summary>

That both inputs and the output share the lifetime `'a`: the returned reference is valid only for as long as *both* `x` and `y` are valid. This lets the compiler check at each call that the result isn't used after either input has gone out of scope.

</details>

**Question #3**

True or false: adding `'a` to a function makes its arguments live longer. Explain.

<details class="solution">
<summary>Show solution</summary>

False. A lifetime annotation *describes* a relationship between references that already exists; it never extends how long any value lives. If the data doesn't actually live long enough, the program is still refused ("does not live long enough"). The fix is to restructure the code or use owned data, not to add more annotations.

</details>

Functions are the first place lifetimes surface. The second is the one lesson [10.8](https://learnrust.net/chapter-10/ownership-in-structs/) explicitly deferred: structs that hold references. The next lesson pays that IOU and shows the `<'a>` on a struct definition.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
