What a lifetime is

On Borrowing and References, the compiler refused a reference that would have outlived the string it pointed to. To do that, it gives every reference a lifetime: the region of the program, from where the reference is created to where it is last used, during which the value it points to must still exist. The borrow checker then checks that each value outlives every reference to it.

Inside a function you never write lifetimes: the compiler sees all the code and infers them. A lifetime is not a time measured while the program runs, and it is not stored anywhere; it exists only while compiling. The question this page answers is what happens at the edges of a function or a type, where the compiler checks each function on its own and only sees signatures.

Lifetime annotations

Here is a small function that returns the later of two timestamps as a meter sends them (YYMMDDhhmmss plus S or W, which compare correctly as text within one season). It returns one of its arguments, a borrowed &str:

fn later(a: &str, b: &str) -> &str {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", later("261007193015S", "261007192500S"));
}
example.rs:1:31: error[E0106]: missing lifetime specifier: expected named lifetime parameter

The compiler does not look into the body to guess, because then a change to the body could silently change what callers are allowed to do. It asks the signature to say it, and writes the answer itself:

error[E0106]: missing lifetime specifier
 --> example.rs:1:31
  |
1 | fn later(a: &str, b: &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 `a` or `b`
help: consider introducing a named lifetime parameter
  |
1 | fn later<'a>(a: &'a str, b: &'a str) -> &'a str {
  |         ++++     ++          ++          ++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0106`.

A lifetime annotation is a name with an apostrophe, such as 'a, written after the & of a reference type (&'a str, &'a mut Vec<u32>) to say which lifetime that reference has. The names are declared in angle brackets after the function name, like type parameters: 'a in fn later<'a> is a lifetime parameter, a placeholder that the compiler fills in with a concrete region at each call. Short names are usual: 'a, 'b, or a descriptive one such as 't for a telegram.

// Timestamps as the meter sends them: YYMMDDhhmmss plus S (summer) or W (winter).
fn later<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a > b { a } else { b }
}

fn main() {
    let electricity = String::from("261007193015S");
    let gas = String::from("261007192500S");
    let newest = later(&electricity, &gas);
    println!("newest reading: {newest}");
}
newest reading: 261007193015S

Read the signature as: "for some region 'a in which both a and b are valid, the result is valid in 'a too." At the call, the compiler picks the largest region that fits both arguments, which is the shorter of the two lifetimes, and checks every use of newest against it.

Annotations describe, they do not extend

An annotation never makes a value live longer. If one of the two strings is dropped while the result is still needed, the program does not compile, even when the result happens to be the other string:

fn later<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a > b { a } else { b }
}

fn main() {
    let electricity = String::from("261007193015S");
    let newest;
    {
        let gas = String::from("261007192500S");
        newest = later(&electricity, &gas);
    }
    println!("newest reading: {newest}");
}
example.rs:10:38: error[E0597]: `gas` does not live long enough: borrowed value does not live long enough
Lines 6 to 13 of the example, with three bars beside them. The bar for electricity runs from line 6 to the end of main at line 13. The bar for gas runs only from line 9 to line 11, where the inner block ends. The bar for the borrow that newest needs runs from line 10 to line 12, where newest is printed. The borrow bar extends past the end of the gas bar, which is exactly what error E0597 reports. electricity gas borrow 'a 6 let electricity = String::from(…); 7 let newest; 8 { 9 let gas = String::from(…); 10 newest = later(&electricity, &gas); 11 } 12 println!("{newest}"); 13 } The borrow is needed until line 12, but gas is dropped at line 11.
Lifetimes as bars. A borrow must fit inside the bar of the value it borrows; here it sticks out below the end of gas.

The signature says that the result may come from b, so the compiler must assume it does, whatever the body would actually return for these two strings. That is the point: callers rely only on the signature, so the function can change its body later without breaking them.

Annotate only what the result borrows from

Tying every parameter to the result is more restrictive than needed when the result can only come from one of them. Here the OBIS code is a slice of line, and the separator is only read during the call, so only line gets the lifetime 't:

// The result borrows from line only; separator may be dropped right after the call.
fn before<'t>(line: &'t str, separator: &str) -> &'t str {
    match line.find(separator) {
        Some(end) => &line[..end],
        None => line,
    }
}

fn main() {
    let line = String::from("1-0:1.7.0(01.234*kW)");
    let obis;
    {
        let separator = String::from("(");
        obis = before(&line, &separator);
    }
    println!("OBIS code: {obis}");
}
OBIS code: 1-0:1.7.0

The OBIS code identifies what a telegram line measures (see OBIS codes). If the body tried to return separator, the function itself would no longer compile, with the error inside the function rather than at every caller.

Outlives relationships

Sometimes two references have different lifetimes and one must last at least as long as the other. An outlives relationship, written 'long: 'short, states that the lifetime 'long lasts at least as long as 'short, so a reference valid for 'long may be used where one valid for 'short is expected. Without it, the branch that returns fallback fails, and the compiler proposes the bound:

error: lifetime may not live long enough
 --> example.rs:2:31
  |
1 | fn label<'short, 'long>(preferred: &'short str, fallback: &'long str) -> &'short str {
  |          ------  ----- lifetime `'long` defined here
  |          |
  |          lifetime `'short` defined here
2 |     if preferred.is_empty() { fallback } else { preferred }
  |                               ^^^^^^^^ function was supposed to return data with lifetime `'short` but it is returning data with lifetime `'long`
  |
  = help: consider adding the following bound: `'long: 'short`
fn label<'short, 'long: 'short>(preferred: &'short str, fallback: &'long str) -> &'short str {
    if preferred.is_empty() { fallback } else { preferred }
}

fn main() {
    let fallback = "unknown meter";      // a literal: lives for the whole program
    for typed in ["M-30004", ""] {
        let input = String::from(typed);
        println!("{}", label(&input, fallback));
    }
}
M-30004
unknown meter

The same notation appears on types: T: 'a means that any references inside the type T are valid for at least 'a. You will mostly read outlives bounds in library signatures rather than write them; when one is missing, the compiler's help line usually names it, as above.

Lifetime elision

Most functions that take and return references need no annotations at all. Lifetime elision is the set of rules by which the compiler fills in lifetimes that a signature leaves out, for the patterns that are nearly always meant:

  1. Each reference parameter without a written lifetime gets its own lifetime parameter.
  2. If there is exactly one input lifetime, it is used for every reference in the result: fn unit(value: &str) -> &str borrows from value.
  3. In a method with &self or &mut self, the result borrows from self.

Elision is pure shorthand: the compiler checks the elided signature exactly as if you had written the lifetimes out. When the rules do not settle the question, as with later, you get E0106 instead of a guess.

struct Telegram {
    text: String,
}

impl Telegram {
    // Elided: the result borrows from self.
    fn header(&self) -> &str {
        self.text.lines().next().unwrap_or("")
    }
}

// Elided: one reference in, so the result borrows from it.
fn unit(value: &str) -> &str {
    match value.split_once('*') {
        Some((_, unit)) => unit,
        None => "",
    }
}

fn main() {
    let telegram = Telegram { text: String::from("/XYZ5\\2DEMO-P1-METER\r\n\r\n1-0:1.7.0(01.234*kW)\r\n!A9EC\r\n") };
    println!("header: {}", telegram.header());
    println!("unit: {}", unit("01.234*kW"));
}
header: /XYZ5\2DEMO-P1-METER
unit: kW

Written out in full, header would be fn header<'s>(&'s self) -> &'s str. The placeholder '_ asks for an elided lifetime where the syntax needs one, for example Line<'_> in a return type, which says "this type borrows something, work out what from the usual rules".

Structs that hold references

A struct field can be a reference, which lets a struct describe a part of some text without copying it. The struct then depends on the borrowed data, and its type must say so with a lifetime parameter. Without one:

struct Line {
    obis: &str,
    value: &str,
}

fn main() {
    let line = Line { obis: "1-0:1.7.0", value: "01.234*kW" };
    println!("{} = {}", line.obis, line.value);
}
example.rs:2:11: error[E0106]: missing lifetime specifier: expected named lifetime parameter
help: consider introducing a named lifetime parameter
  |
1 ~ struct Line<'a> {
2 ~     obis: &'a str,
  |

In this example, a developer on Kestrel's platform team prototypes a telegram reader that does not copy anything: each Line<'a> holds two slices into the telegram text, the OBIS code and the value in brackets. The 'a in Line<'a> reads "a line that borrows from something that lives for 'a". The telegram is a shortened version of the one on Smart Meters and the P1 Port, and the power value 1.234 kW becomes 1234 W, as there:

const TELEGRAM: &str = "/XYZ5\\2DEMO-P1-METER\r\n\
\r\n\
1-0:1.8.1(000412.338*kWh)\r\n\
1-0:1.8.2(000467.905*kWh)\r\n\
1-0:1.7.0(01.234*kW)\r\n\
1-0:2.7.0(00.000*kW)\r\n\
!406A\r\n";

/// One data line of a telegram, borrowed from the telegram text: nothing is copied.
#[derive(Debug)]
struct Line<'a> {
    obis: &'a str,
    value: &'a str,
}

impl<'a> Line<'a> {
    /// Splits "1-0:1.7.0(01.234*kW)" into its OBIS code and the value in brackets.
    fn parse(text: &'a str) -> Option<Line<'a>> {
        let (obis, rest) = text.split_once('(')?;   // ? returns None if there is no '('
        let value = rest.strip_suffix(')')?;
        Some(Line { obis, value })
    }

    /// The value without its unit, e.g. "01.234".
    fn number(&self) -> &'a str {
        match self.value.split_once('*') {
            Some((number, _)) => number,
            None => self.value,
        }
    }
}

fn main() {
    let telegram = String::from(TELEGRAM);
    let mut lines = Vec::new();
    for text in telegram.lines() {
        if let Some(line) = Line::parse(text) {
            lines.push(line);
        }
    }
    println!("{} data lines, first {:?}", lines.len(), lines[0]);

    for line in &lines {
        if line.obis == "1-0:1.7.0" {
            let watts: u32 = line.number().replace('.', "").parse().unwrap_or(0);
            println!("power now: {watts} W");
        }
    }
}
4 data lines, first Line { obis: "1-0:1.8.1", value: "000412.338*kWh" }
power now: 1234 W

The header line, the empty line and the CRC line !406A have no brackets, so parse returns None for them. impl<'a> Line<'a> declares the lifetime for the whole impl block, so its methods can use it: number returns &'a str, a slice of the telegram, rather than a slice of the Line value, which elision would have chosen. That distinction lets a caller keep the number after the Line itself is gone, as long as the telegram is there. The conversion to watts assumes three decimals, as the telegram format uses for kW values; a real parser would check that, and handle errors instead of falling back to 0 (see Error Handling).

The price of zero-copy is that the lines can never outlive the telegram. Freeing the text while a line is still in use, for example to make room for the next telegram, is refused:

struct Line<'a> {
    obis: &'a str,
    value: &'a str,
}

fn parse(text: &str) -> Option<Line<'_>> {
    let (obis, rest) = text.split_once('(')?;
    let value = rest.strip_suffix(')')?;
    Some(Line { obis, value })
}

fn read_telegram() -> String {
    String::from("1-0:1.7.0(01.234*kW)\r\n1-0:2.7.0(00.000*kW)\r\n")
}

fn main() {
    let telegram = read_telegram();
    let first = parse(telegram.lines().next().unwrap_or("")).unwrap();
    drop(telegram);                 // free the text: the next telegram arrives
    println!("{} = {}", first.obis, first.value);
}
example.rs:19:10: error[E0505]: cannot move out of `telegram` because it is borrowed: move out of `telegram` occurs here

In C, the same design, a struct of char * pointers into a receive buffer that is then reused, compiles and silently reads the next telegram's bytes. When a line must outlive its telegram, copy the parts you need into owned String values first:

struct Line<'a> {
    obis: &'a str,
    value: &'a str,
}

fn parse(text: &str) -> Option<Line<'_>> {
    let (obis, rest) = text.split_once('(')?;
    let value = rest.strip_suffix(')')?;
    Some(Line { obis, value })
}

fn read_telegram() -> String {
    String::from("1-0:1.7.0(01.234*kW)\r\n1-0:2.7.0(00.000*kW)\r\n")
}

fn main() {
    let telegram = read_telegram();
    let first = parse(telegram.lines().next().unwrap_or("")).unwrap();
    let obis = first.obis.to_string();      // owned copies of the two pieces
    let value = first.value.to_string();
    drop(telegram);                         // fine: first is no longer used
    println!("{obis} = {value}");
}
1-0:1.7.0 = 01.234*kW

The 'static lifetime

The 'static lifetime is the lifetime of data that stays valid for the whole run of the program. String literals have type &'static str: their bytes are part of the program file and are never freed. A function that always returns one of a fixed set of literals can say so, and then its result does not borrow from its argument at all:

fn tariff_name(code: &str) -> &'static str {
    match code {
        "0001" => "low",
        "0002" => "normal",
        _ => "unknown",
    }
}

static METER_PREFIX: &str = "M-";

fn main() {
    let from_telegram = String::from("0002");
    let name = tariff_name(&from_telegram);
    drop(from_telegram);            // fine: name does not borrow from it
    println!("tariff {name}, meters start with {METER_PREFIX}");
}
tariff normal, meters start with M-

0001 and 0002 are the tariff indicator values a Dutch meter sends in its telegram. A static item, like METER_PREFIX, also lives for the whole program, so references to it are 'static too.

You will also meet 'static as a bound, for example T: 'static on std::thread::spawn. That does not mean the value lives forever. It means the type contains no references that could expire, so the new thread may keep the value as long as it likes. An owned String satisfies 'static; a &str borrowed from a local variable does not, which is why a thread cannot simply borrow the caller's variables (see Concurrency).

help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
1 | fn latest_label() -> &'static String {
  |                       +++++++
help: instead, you are more likely to want to return an owned value
  |
1 - fn latest_label() -> &String {
1 + fn latest_label() -> String {
  |

The second suggestion is the right one: a function that creates a new value returns it owned, as on Borrowing and References. Making data truly 'static at run time is possible (Box::leak deliberately never frees a value), but it is a leak, and only appropriate for data that really must live until the program ends.

Reading lifetime errors

Lifetime errors look abstract, but they always describe a concrete situation: some value is dropped, moved or changed while a reference to it is still needed. The labels in the full message give the three lines that matter. The errors on this page and the previous one:

Error What it means Usual fix
E0106 missing lifetime specifier a signature or struct has a reference whose source the elision rules cannot determine add the lifetime parameter the compiler proposes, or return or store an owned value
E0597 does not live long enough a reference is used after its value's scope ended move the value to an outer scope, or use the reference earlier
E0505 cannot move out because it is borrowed a value is moved or dropped while still borrowed finish using the borrow first, or copy what you need
E0515 cannot return reference to local variable a function returns a reference to its own local value return the value itself
lifetime may not live long enough a function returns data with a different lifetime than its signature promises fix the signature, often with an outlives bound the help line names

When the fixes keep multiplying, step back and ask who should own the data. A reference is the right tool for short, clear relationships; data that is shared for a long time or by several owners is better held in owned values or in shared-ownership types such as Rc and Arc (see Smart Pointers).