Lifetimes
Abstractions · Lesson 37
A lifetime is the stretch of the program during which a reference is valid. Every reference has one, and the compiler usually works it out. You write one, like 'a, when a signature or a struct has to say which values a reference borrows from. It describes a link the compiler checks. It never makes anything live longer.
Coming from GoGo has no lifetimes: the garbage collector keeps whatever you can still reach alive. Rust has no collector, so a function that returns a reference, or a struct that stores one, names the input it borrows from with 'a, and the compiler checks the link.
Coming from KotlinThe JVM keeps an object alive while any reference to it exists, so there is nothing to annotate. In Rust 'a is a label saying which inputs a returned or stored reference borrows from. The compiler checks the label; it does not change how long anything lives.
Coming from PythonPython keeps an object alive while a name or container still refers to it, so nothing needs naming. In Rust, 'a is a label for "this reference borrows from that input", and the compiler rejects any use of it after the input is gone.
Coming from TypeScriptJavaScript's garbage collector keeps an object alive while it is reachable, and TypeScript's types never mention it. In Rust a signature or struct that holds a reference says which value it borrows from ('a), and the compiler checks every use.
Coming from JavaJava keeps an object alive while any reference to it exists, so no signature says how long a result is valid. In Rust 'a says it: the result borrows from these inputs and is valid while they are.
Coming from C#The .NET garbage collector keeps an object alive while it is reachable. The nearest compile-time ideas are Span<T>, which the compiler stops from escaping, and the scoped keyword; Rust applies a check of this kind to every reference and names the link with 'a.
Coming from CA C pointer carries no lifetime, so which input a returned pointer points into, and until when, lives in a comment. Rust writes it in the signature ('a) and checks every use.
Coming from C++A C++ reference or std::string_view carries no lifetime in its type either. Compilers flag a few recognised shapes, and clang has an opt-in [[clang::lifetimebound]] attribute, but nothing checks every call. 'a is the same contract, written in the signature and checked everywhere.
The quick comparison
// 'a says: the result borrows from both inputs, so it is valid only while both are.fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
// A struct that holds a reference says how long the borrowed text must live.struct Excerpt<'a> { part: &'a str,}
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first = novel.split('.').next().unwrap_or(""); let excerpt = Excerpt { part: first }; let l = longest(excerpt.part, "short"); println!("longest: {l}"); println!("excerpt length: {}", excerpt.part.len());}longest: Call me Ishmael excerpt length: 15
package main
import ( "fmt" "strings")
// Nothing to annotate: the garbage collector keeps the text alive while anyone uses it.func longest(a, b string) string { if len(a) >= len(b) { return a } return b}
type Excerpt struct { part string}
func main() { novel := "Call me Ishmael. Some years ago..." first, _, _ := strings.Cut(novel, ".") excerpt := Excerpt{part: first} l := longest(excerpt.part, "short") fmt.Println("longest:", l) fmt.Println("excerpt length:", len(excerpt.part))}longest: Call me Ishmael excerpt length: 15
// Nothing to annotate: the JVM keeps a String alive while any reference to it exists.fun longest(a: String, b: String): String = if (a.length >= b.length) a else b
class Excerpt(val part: String)
fun main() { val novel = "Call me Ishmael. Some years ago..." val first = novel.substringBefore('.') val excerpt = Excerpt(first) val l = longest(excerpt.part, "short") println("longest: $l") println("excerpt length: ${excerpt.part.length}")}longest: Call me Ishmael excerpt length: 15
# Nothing to annotate: an object stays alive while anything refers to it.def longest(a, b): return a if len(a) >= len(b) else b
class Excerpt: def __init__(self, part): self.part = part
novel = "Call me Ishmael. Some years ago..."first = novel.split(".")[0]excerpt = Excerpt(first)l = longest(excerpt.part, "short")print("longest:", l)print("excerpt length:", len(excerpt.part))longest: Call me Ishmael excerpt length: 15
// Nothing to annotate: the garbage collector keeps a string alive while it is reachable.function longest(a: string, b: string): string { return a.length >= b.length ? a : b;}
class Excerpt { constructor(readonly part: string) {}}
const novel = "Call me Ishmael. Some years ago...";const first = novel.split(".")[0];const excerpt = new Excerpt(first);const l = longest(excerpt.part, "short");console.log("longest:", l);console.log("excerpt length:", excerpt.part.length);longest: Call me Ishmael excerpt length: 15
public class Main { // Nothing to annotate: the JVM keeps a String alive while any reference to it exists. static String longest(String a, String b) { return a.length() >= b.length() ? a : b; }
record Excerpt(String part) {}
public static void main(String[] args) { String novel = "Call me Ishmael. Some years ago..."; String first = novel.substring(0, novel.indexOf('.')); Excerpt excerpt = new Excerpt(first); String l = longest(excerpt.part(), "short"); System.out.println("longest: " + l); System.out.println("excerpt length: " + excerpt.part().length()); }}longest: Call me Ishmael excerpt length: 15
// Nothing to annotate: the garbage collector keeps a string alive while it is reachable.static string Longest(string a, string b) => a.Length >= b.Length ? a : b;
string novel = "Call me Ishmael. Some years ago...";string first = novel.Substring(0, novel.IndexOf('.'));var excerpt = new Excerpt(first);string l = Longest(excerpt.Part, "short");Console.WriteLine($"longest: {l}");Console.WriteLine($"excerpt length: {excerpt.Part.Length}");
record Excerpt(string Part);longest: Call me Ishmael excerpt length: 15
#include <stdio.h>#include <string.h>
// A pointer and a length, like &str. Nothing in the types says the result of// longest() points into a or b, or for how long that stays valid: that is a comment's job.struct View { const char *ptr; size_t len;};
static struct View longest(struct View a, struct View b) { return a.len >= b.len ? a : b;}
struct Excerpt { struct View part;};
int main(void) { const char *novel = "Call me Ishmael. Some years ago..."; struct View first = {novel, (size_t)(strchr(novel, '.') - novel)}; struct Excerpt excerpt = {first}; struct View other = {"short", 5}; struct View l = longest(excerpt.part, other); printf("longest: %.*s\n", (int)l.len, l.ptr); printf("excerpt length: %zu\n", excerpt.part.len); return 0;}longest: Call me Ishmael excerpt length: 15
#include <iostream>#include <string>#include <string_view>
// Nothing in the types says the result points into a or b. Both are views of// someone else's characters, and keeping them valid is up to you.std::string_view longest(std::string_view a, std::string_view b) { return a.size() >= b.size() ? a : b;}
struct Excerpt { std::string_view part;};
int main() { std::string novel = "Call me Ishmael. Some years ago..."; std::string_view first = std::string_view(novel).substr(0, novel.find('.')); Excerpt excerpt{first}; std::string_view l = longest(excerpt.part, "short"); std::cout << "longest: " << l << "\n"; std::cout << "excerpt length: " << excerpt.part.size() << "\n";}longest: Call me Ishmael excerpt length: 15
What's different
'ais a name.longest<'a>(a: &'a str, b: &'a str) -> &'a strsays: the result borrows fromaand fromb, so it is valid only while both are. The signature describes the link; the compiler checks it. Writing'anever keeps a value alive.- Most signatures need none. When the inputs have exactly one lifetime, or the method has
&self, the compiler fills in the obvious link (the elision rules). You write'awhen two or more inputs could be the source, or when a struct holds a reference. struct Excerpt<'a> { part: &'a str }says anExcerptcannot outlive the text itspartpoints into. A struct that owns its data (String,Vec) has no lifetime to write, which is why the Structs lesson had none.'staticis the longest lifetime: string literals have it, andT: 'staticmeans "Tholds no borrow that ends early". Code that owns its data (String,Vec) writes few lifetimes, and when they start to pile up, owning the data is usually the way out.
A result used after one of its inputs is gone
Looks fine: in Go, Kotlin, Python, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.
Fine in Go. Rust can't prove it's safe.Fine in Kotlin. Rust can't prove it's safe.Fine in Python. Rust can't prove it's safe.Fine in TypeScript. Rust can't prove it's safe.Fine in Java. Rust can't prove it's safe.Fine in C#. Rust can't prove it's safe.Fine in C. Rust can't prove it's safe.Fine in C++. Rust can't prove it's safe.fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let s1 = String::from("a long first string"); let result; { let s2 = String::from("short"); result = longest(&s1, &s2); } println!("longest: {result}");}package main
import ( "fmt" "strings")
func longest(a, b string) string { if len(a) >= len(b) { return a } return b}
// join builds a new string at run time: a heap object, not a literal.func join(parts ...string) string { return strings.Join(parts, " ")}
func main() { s1 := join("a", "long", "first", "string") var result, other string { s2 := join("short", "string") result = longest(s1, s2) s3 := join("an", "even", "longer", "second", "string") other = longest(s1, s3) } fmt.Println("longest:", result) fmt.Println("with a longer s3:", other)}longest: a long first string with a longer s3: an even longer second string
fun longest(a: String, b: String): String = if (a.length >= b.length) a else b
// join builds a new String at run time: a heap object, not a literal.fun join(vararg parts: String): String = parts.joinToString(" ")
fun main() { val s1 = join("a", "long", "first", "string") var result = "" var other = "" run { val s2 = join("short", "string") result = longest(s1, s2) val s3 = join("an", "even", "longer", "second", "string") other = longest(s1, s3) } println("longest: $result") println("with a longer s3: $other")}longest: a long first string with a longer s3: an even longer second string
def longest(a, b): return a if len(a) >= len(b) else b
def join(*parts): # builds a new str at run time, not a constant return " ".join(parts)
s1 = join("a", "long", "first", "string")if True: s2 = join("short", "string") result = longest(s1, s2) s3 = join("an", "even", "longer", "second", "string") other = longest(s1, s3)print("longest:", result)print("with a longer s3:", other)longest: a long first string with a longer s3: an even longer second string
function longest(a: string, b: string): string { return a.length >= b.length ? a : b;}
// join builds a new string at run time, not a constant.function join(...parts: string[]): string { return parts.join(" ");}
const s1 = join("a", "long", "first", "string");let result: string;let other: string;{ const s2 = join("short", "string"); result = longest(s1, s2); const s3 = join("an", "even", "longer", "second", "string"); other = longest(s1, s3);}console.log("longest:", result);console.log("with a longer s3:", other);longest: a long first string with a longer s3: an even longer second string
public class Main { static String longest(String a, String b) { return a.length() >= b.length() ? a : b; }
// join builds a new String at run time: a heap object, not a literal. static String join(String... parts) { return String.join(" ", parts); }
public static void main(String[] args) { String s1 = join("a", "long", "first", "string"); String result, other; { String s2 = join("short", "string"); result = longest(s1, s2); String s3 = join("an", "even", "longer", "second", "string"); other = longest(s1, s3); } System.out.println("longest: " + result); System.out.println("with a longer s3: " + other); }}longest: a long first string with a longer s3: an even longer second string
static string Longest(string a, string b) => a.Length >= b.Length ? a : b;
// Join builds a new string at run time: a heap object, not a literal.static string Join(params string[] parts) => string.Join(" ", parts);
string s1 = Join("a", "long", "first", "string");string result, other;{ string s2 = Join("short", "string"); result = Longest(s1, s2); string s3 = Join("an", "even", "longer", "second", "string"); other = Longest(s1, s3);}Console.WriteLine($"longest: {result}");Console.WriteLine($"with a longer s3: {other}");longest: a long first string with a longer s3: an even longer second string
#include <stdio.h>#include <string.h>
static const char *longest(const char *a, const char *b) { return strlen(a) >= strlen(b) ? a : b;}
int main(void) { char s1[] = "a long first string"; const char *result; { char s2[] = "short"; result = longest(s1, s2); } printf("longest: %s\n", result); return 0;}longest: a long first string
#include <iostream>#include <string>
const std::string &longest(const std::string &a, const std::string &b) { return a.size() >= b.size() ? a : b;}
int main() { std::string s1 = "a long first string"; const std::string *result; { std::string s2 = "short"; result = &longest(s1, s2); } std::cout << "longest: " << *result << "\n";}longest: a long first string
What does the compiler say? Guess first, then open.
error[E0597]: `s2` does not live long enough --> src/main.rs:10:31 | 9 | let s2 = String::from("short"); | -- binding `s2` declared here 10 | result = longest(&s1, &s2); | ^^^ borrowed value does not live long enough 11 | } | - `s2` dropped here while still borrowed 12 | println!("longest: {result}"); | ------ borrow later used here
Go runs this and prints both lines. The strings are built at run time, so they are garbage-collected values, not literals. s2 and s3 are only names that went out of scope: the second line shows that result keeps the longer string alive after the block ends.
Kotlin runs this and prints both lines. The strings are built at run time. The longer s3 is out of scope after the block, but the string stays alive while other refers to it, and nothing in the signature says which input it is.
Python prints both lines. The strings are built at run time. Python has no block scope, so s2 and s3 are still defined names afterwards, and each string stays alive while anything refers to it.
Prints both lines. s3 is out of scope after the block, but its string stays alive while other can reach it.
Java runs this and prints both lines. s3 is out of scope after the block, but the String it held stays alive as long as other refers to it.
C# runs this and prints both lines. The string from s3 stays alive while other can reach it, whatever happened to the variable.
C runs this and prints the long string here, because the result is s1. Nothing in a C signature says the result points into a or b. With a shorter s1 and a longer s2, result points into an array that is out of scope after the block, and AddressSanitizer reports stack-use-after-scope (the C and C++ block below shows the run).
C++ runs this and prints the long string here, because the result is s1. The signature returns a reference but does not say into which argument. With a shorter s1 and a longer s2, result points at a destroyed std::string, and AddressSanitizer reports heap-use-after-free (the C and C++ block below shows the run).
The signature says the result may borrow from a or from b; which one depends on the lengths at run time. The compiler checks the call with the signature alone, so it must assume the result could be s2, which is dropped at the closing } while result is still used.
This run would be fine, because s1 is the longer string. The compiler reasons from types, not from values, and with a shorter s1 the same code would read freed memory, so it refuses both. The six garbage-collected languages never break on this shape, because the collector keeps s2's string alive for as long as result refers to it. In C and C++ the shorter-s1 version is a real use after free. Adding more 'a annotations cannot help: they only describe the link, they do not lengthen it. Keep s2 in scope as long as result is used, as fixed.rs does, or return an owned String.
The fix
Compiles and runs.
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let s1 = String::from("a long first string"); let s2 = String::from("short"); let result = longest(&s1, &s2); println!("longest: {result}");}longest: a long first string
The toolkit
When you write a lifetime
A lifetime is how long a reference may be used: a region of the program, not a number, and it must fit inside the time the value it points at is alive (Borrowing showed where a region ends). The compiler works one out for every reference. You write one, like 'a, only when a signature or a struct has to say which value a reference borrows from.
That is less often than the notation suggests. Code that keeps String and Vec in its structs writes few; parsers, tokenizers and other code that hands out pieces of its input write them every day. You will meet 'a:
- on a function with two or more reference inputs that returns a reference (
longest); - on a struct that holds a reference, and on its methods that hand out pieces of it (structs);
- when a bound asks for
'static, asthread::spawnandBox<dyn Trait>do ('static).
Naming a lifetime: 'a
'a is a lifetime parameter: an apostrophe and a name, declared in angle brackets like the type parameter in Generics and written after the &.
// 'a says: the result borrows from both inputs, so it is valid only while both are.fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("lifetimes"); let second = String::from("regions"); let result = longest(&first, &second); println!("longest: {result}");}longest: lifetimes
'a ties a, b and the result together: the result may be used only while both inputs are alive. At each call the compiler picks the stretch where both arguments are valid (the Book explains the choice).
'a does not extend a lifetime. The gotcha above has 'a on every reference and still fails, because s2 is dropped before result is used. The annotation describes how references relate. It cannot keep s2 alive.
| Written | Reads as |
|---|---|
&str |
a reference; the compiler works out the lifetime |
&'a str, &'a mut Vec<i32> |
a reference valid for 'a |
fn f<'a>(x: &'a str) -> &'a str |
the result is valid as long as x is |
struct Parser<'a> |
a parser that borrows something for 'a |
Parser<'_> |
'_: some lifetime, and I do not need its name |
&'static str |
a reference valid for the whole run of the program |
To read a signature that has 'a, find the lifetime on the result and look for the same name on the inputs. Those are the values the caller must keep alive while it uses the result.
Leaving them out: the elision rules
You wrote fn first_word(text: &str) -> &str in earlier lessons with no 'a. The compiler fills the lifetimes in by three rules (the Reference states them), applied in this order to a function signature:
| Rule | Example | The compiler reads |
|---|---|---|
| 1. Every elided input lifetime is a separate parameter | fn tail<'a>(text: &'a str, skip: &str) -> &'a str |
fn tail<'a, 'b>(text: &'a str, skip: &'b str) -> &'a str |
| 2. If the inputs have exactly one lifetime, the output gets it | fn first_word(text: &str) -> &str |
fn first_word<'a>(text: &'a str) -> &'a str |
3. With &self or &mut self, the output gets the lifetime of self |
fn label(&self, prefix: &str) -> &str |
fn label<'a, 'b>(&'a self, prefix: &'b str) -> &'a str |
// Rule 2: one input lifetime is given to the output.fn first_word(text: &str) -> &str { text.split(' ').next().unwrap_or("")}fn first_word_written<'a>(text: &'a str) -> &'a str { text.split(' ').next().unwrap_or("")}
// Rule 1: every elided input gets its own lifetime. `skip` is not tied to the result.fn tail<'a>(text: &'a str, skip: &str) -> &'a str { text.strip_prefix(skip).unwrap_or(text)}
fn tail_written<'a, 'b>(text: &'a str, skip: &'b str) -> &'a str { text.strip_prefix(skip).unwrap_or(text)}
struct Library { name: String,}
impl Library { // Rule 3: with &self, the output gets the lifetime of self. fn label(&self, _prefix: &str) -> &str { &self.name } fn label_written<'a, 'b>(&'a self, _prefix: &'b str) -> &'a str { &self.name }}
fn main() { println!("first_word: {}", first_word("hello rust")); println!("first_word_written: {}", first_word_written("hello rust"));
let text = String::from("re:lifetimes"); let result; { let skip = String::from("re:"); result = tail(&text, &skip); } // `skip` is gone, `result` is still used below println!("tail: {result}"); println!("tail_written: {}", tail_written("re:again", "re:"));
let library = Library { name: String::from("city library"), }; let label; { let prefix = String::from("main branch"); label = library.label(&prefix); } // `prefix` is gone, `label` is still used below println!("label: {label}"); println!("label_written: {}", library.label_written("x"));}first_word: hello first_word_written: hello tail: lifetimes tail_written: again label: city library label_written: city library
Each function appears twice, as you write it and as the compiler reads it, and they behave the same. skip and prefix are dropped while result and label are still in use, which compiles because rules 1 and 3 keep those inputs out of the result’s lifetime.
With two reference inputs and no self, no rule gives the output a lifetime, and you must write one. That is E0106, the error Returning references walks through, with a table of which signatures compile. Its help: suggests the same 'a on every reference, which is right for longest. It is the wrong fix when the result comes from only one input. Here the result comes from text, and tying it to skip as well makes the caller keep skip alive:
fn tail<'a>(text: &'a str, skip: &'a str) -> &'a str { text.strip_prefix(skip).unwrap_or(text)}
fn main() { let text = String::from("re:lifetimes"); let result; { let skip = String::from("re:"); result = tail(&text, &skip); } println!("tail: {result}");}error[E0597]: `skip` does not live long enough --> src/main.rs:10:30 | 9 | let skip = String::from("re:"); | ---- binding `skip` declared here 10 | result = tail(&text, &skip); | ^^^^^ borrowed value does not live long enough 11 | } | - `skip` dropped here while still borrowed 12 | println!("tail: {result}"); | ------ borrow later used here
Give each input the lifetime it needs, as tail did in the program above.
Rule 3 has a catch of its own. It ties the result to self even when the body returns another parameter:
struct Library { name: String,}
impl Library { // Rule 3 ties the result to `self`, but this returns `other`. fn pick(&self, other: &str) -> &str { other }}
fn main() { let library = Library { name: String::from("city library"), }; println!("{}: {}", library.name, library.pick("elsewhere"));}error: lifetime may not live long enough --> src/main.rs:8:9 | 7 | fn pick(&self, other: &str) -> &str { | - - let's call the lifetime of this reference `'1` | | | let's call the lifetime of this reference `'2` 8 | other | ^^^^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1` | help: consider introducing a named lifetime parameter and update trait if needed | 7 | fn pick<'a>(&self, other: &'a str) -> &'a str { | ++++ ++ ++
This error has no code. The help: is the fix: name the parameter’s lifetime and put it on the result.
Structs that hold references
A struct field that is a reference needs a lifetime parameter, because the compiler cannot guess it (Structs showed the error). The struct then cannot outlive the text its field points into:
use std::mem::size_of;
struct Excerpt<'a> { part: &'a str,}
fn main() { let text = String::from("Call me Ishmael. Some years ago"); let part = text.split('.').next().unwrap_or(""); let excerpt = Excerpt { part }; println!("text.len(): {}", text.len()); println!("text.capacity(): {}", text.capacity()); println!("excerpt.part: {:?}", excerpt.part); println!("excerpt.part.len(): {}", excerpt.part.len()); println!("same address: {}", excerpt.part.as_ptr() == text.as_ptr()); println!("size_of::<&str>(): {}", size_of::<&str>()); println!("size_of::<Excerpt>(): {}", size_of::<Excerpt>());}text.len(): 31 text.capacity(): 31 excerpt.part: "Call me Ishmael" excerpt.part.len(): 15 same address: true size_of::<&str>(): 16 size_of::<Excerpt>(): 16
excerpt.part points at the first 15 bytes of text's buffer. It owns nothing and copied nothing. The lifetime 'a is a fact the compiler checked, not a field: Excerpt is 16 bytes on a 64-bit target, the size of a &str.Dropping the text while the struct is still in use is the same E0597 as before:
struct Excerpt<'a> { part: &'a str,}
fn main() { let excerpt; { let text = String::from("Call me Ishmael. Some years ago"); excerpt = Excerpt { part: &text[..15] }; } println!("excerpt: {}", excerpt.part);}error[E0597]: `text` does not live long enough --> src/main.rs:9:36 | 8 | let text = String::from("Call me Ishmael. Some years ago"); | ---- binding `text` declared here 9 | excerpt = Excerpt { part: &text[..15] }; | ^^^^ borrowed value does not live long enough 10 | } | - `text` dropped here while still borrowed 11 | println!("excerpt: {}", excerpt.part); | ------------ borrow later used here
Methods repeat the name. impl<'a> Parser<'a> declares 'a once and uses it in the type, like impl<T> Stack<T>. What matters is the return type of next_word:
struct Parser<'a> { rest: &'a str,}
impl<'a> Parser<'a> { fn new(input: &'a str) -> Parser<'a> { Parser { rest: input } }
// The word borrows from the text (`'a`), not from the parser. fn next_word(&mut self) -> Option<&'a str> { let rest = self.rest.trim_start(); if rest.is_empty() { return None; } let (word, tail) = rest.split_once(' ').unwrap_or((rest, "")); self.rest = tail; Some(word) }}
fn main() { let text = String::from("lifetimes are regions"); let mut parser = Parser::new(&text); let a = parser.next_word(); let b = parser.next_word(); // `a` is still alive here println!("a: {a:?}"); println!("b: {b:?}"); println!("c: {:?}", parser.next_word()); println!("d: {:?}", parser.next_word());}a: Some("lifetimes")
b: Some("are")
c: Some("regions")
d: NoneThe word is Option<&'a str>: it borrows from the text, not from the parser. So a is still valid after the second call, which needs &mut parser again. Leave the 'a off the result and rule 3 ties it to &mut self instead:
struct Parser<'a> { rest: &'a str,}
impl<'a> Parser<'a> { // No 'a on the result: by rule 3 it borrows from `&mut self`. fn next_word(&mut self) -> Option<&str> { let rest = self.rest.trim_start(); if rest.is_empty() { return None; } let (word, tail) = rest.split_once(' ').unwrap_or((rest, "")); self.rest = tail; Some(word) }}
fn main() { let text = String::from("lifetimes are regions"); let mut parser = Parser { rest: &text }; let a = parser.next_word(); let b = parser.next_word(); println!("a: {a:?}, b: {b:?}");}error[E0499]: cannot borrow `parser` as mutable more than once at a time --> src/main.rs:22:13 | 21 | let a = parser.next_word(); | ------ first mutable borrow occurs here 22 | let b = parser.next_word(); | ^^^^^^ second mutable borrow occurs here 23 | println!("a: {a:?}, b: {b:?}"); | - first borrow later used here
Both versions have the same body. The only difference is which lifetime the signature promises. A method that hands out pieces of the borrowed data should say 'a for them.
When you do not need the name, '_ says “some lifetime”: impl Parser<'_>, fn describe(parser: &Parser<'_>), and Formatter<'_> in the signatures you meet in std. It is the other lifetime that needs no declaration.
use std::fmt;
struct Parser<'a> { rest: &'a str,}
// `'_` means "some lifetime, and I do not need its name".impl Parser<'_> { fn remaining(&self) -> usize { self.rest.len() }}
impl fmt::Display for Parser<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Parser({:?})", self.rest) }}
fn describe(parser: &Parser<'_>) -> usize { parser.remaining()}
fn main() { let parser = Parser { rest: "left over" }; println!("remaining: {}", describe(&parser)); println!("display: {parser}");}remaining: 9
display: Parser("left over")Rule 2 counts lifetimes, not references. &Parser has two, the borrow itself and the 'a inside the parser, so a function that takes it and returns a &str has no single lifetime to give the output:
struct Parser<'a> { rest: &'a str,}
// `&Parser` has two lifetimes: the borrow of the parser and the `'a` inside it.fn rest(parser: &Parser) -> &str { parser.rest}
fn main() { let parser = Parser { rest: "left over" }; println!("rest: {}", rest(&parser));}error[E0106]: missing lifetime specifier --> src/main.rs:6:29 | 6 | fn rest(parser: &Parser) -> &str { | ------- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say which one of `parser`'s 2 lifetimes it is borrowed from help: consider introducing a named lifetime parameter | 6 | fn rest<'a>(parser: &'a Parser<'a>) -> &'a str { | ++++ ++ ++++ ++
Writing fn rest<'a>(parser: &Parser<'a>) -> &'a str says the piece comes from the text the parser holds.
'static
'static is a named lifetime you never declare: the whole run of the program. ('_ is the only other lifetime that needs no declaration.) It appears in two ways that are easy to mix up.
struct Noisy(&'static str);
impl Drop for Noisy { fn drop(&mut self) { println!("dropped {}", self.0); }}
// T: 'static means "T holds no borrow that could end early". It is not "T lives forever".fn consume<T: 'static>(_value: T) { println!("consume returns");}
fn main() { consume(String::from("an owned String is fine")); consume(Noisy("a")); println!("after consume");}consume returns consume returns dropped a after consume
- A reference valid for the whole run,
&'static str. A string literal is one (Returning references shows it, and the other ways to get one). - A bound,
T: 'static: “Tholds no borrow that could end early”. An ownedStringsatisfies it. It does not mean the value lives forever:Noisywas dropped whenconsumereturned. A reference to a local does not satisfy it:
fn consume<T: 'static>(_value: T) {}
fn main() { let name = String::from("local"); consume(&name);}error[E0597]: `name` does not live long enough --> src/main.rs:5:13 | 4 | let name = String::from("local"); | ---- binding `name` declared here 5 | consume(&name); | --------^^^^^- | | | | | borrowed value does not live long enough | argument requires that `name` is borrowed for `'static` 6 | } | - `name` dropped here while still borrowed | note: requirement that the value outlives `'static` introduced here --> src/main.rs:1:15 | 1 | fn consume<T: 'static>(_value: T) {} | ^^^^^^^
A Box<dyn Trait> in a signature means Box<dyn Trait + 'static>. To store a trait object that borrows, say so:
trait Describe { fn describe(&self) -> String;}
struct Label<'a> { text: &'a str,}
impl Describe for Label<'_> { fn describe(&self) -> String { format!("label {}", self.text) }}
// `Box<dyn Describe>` would mean `Box<dyn Describe + 'static>`. This one borrows `text`.fn boxed<'a>(text: &'a str) -> Box<dyn Describe + 'a> { Box::new(Label { text })}
fn main() { let text = String::from("borrowed"); let item = boxed(&text); println!("describe: {}", item.describe());}describe: label borrowed
std::thread::spawn puts 'static on its closure (docs), which is why a closure that borrows a local needs move (Threads).
Lifetime errors and the usual fix
| Error | The compiler says | The usual fix |
|---|---|---|
| E0106 | missing lifetime specifier | Write the 'a (above), or return an owned value |
| E0597 | x does not live long enough |
Declare the owner earlier so it lives as long as the borrow, or own the data |
| E0716 | temporary value dropped while borrowed | Give the temporary a name with let (example below) |
| E0505 | cannot move out of x because it is borrowed |
Finish with the reference first, or do not move (Borrowing) |
| E0499 | cannot borrow x as mutable more than once at a time |
End the first borrow earlier, or give the method’s result the right 'a (above); see Mutable borrows |
| E0502 | cannot borrow x as immutable because it is also borrowed as mutable |
End the first borrow earlier, or restructure (below) |
| E0515 | cannot return reference to local variable x |
Return an owned value (Returning references) |
| E0373 | closure may outlive the current function, but it borrows x |
Add move (Closures) |
| no code | lifetime may not live long enough | The body returns more than the signature promises: change one of them |
E0597 marks three places: where the reference is made, where the owner is dropped, and where the reference is used last. A fix moves one of the three. Declare the owner earlier, use the reference before the owner goes, or stop borrowing and keep a copy. The first and third:
struct Excerpt<'a> { part: &'a str,}
struct OwnedExcerpt { part: String,}
fn main() { // Fix 1: declare the owner first, so it outlives the struct that borrows from it. let text = String::from("Call me Ishmael. Some years ago"); let excerpt = Excerpt { part: &text[..15] }; println!("borrowed: {}", excerpt.part);
// Fix 3: keep a copy. The struct no longer depends on `temp`. let owned; { let temp = String::from("Call me Ishmael. Some years ago"); owned = OwnedExcerpt { part: temp[..15].to_string(), }; } println!("owned: {}", owned.part);}borrowed: Call me Ishmael owned: Call me Ishmael
E0716 can appear with no function in sight. String::from("temporary") has no name, so it is dropped at the end of the statement, and as_str() borrowed from it:
fn main() { let word = String::from("temporary").as_str(); println!("word: {word}");}error[E0716]: temporary value dropped while borrowed --> src/main.rs:2:16 | 2 | let word = String::from("temporary").as_str(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use 3 | println!("word: {word}"); | ---- borrow later used here | help: consider using a `let` binding to create a longer lived value | 2 ~ let binding = String::from("temporary"); 3 ~ let word = binding.as_str();
Bind the String with let first.
When to own instead of borrow
A lifetime on a struct is a cost you pay in every type that holds it. Owning the data copies it once and removes the lifetime. The structs in the Structs lesson owned their fields, so they needed none.
use std::mem::size_of;
// Borrowing: nothing is copied, but the lifetime spreads to every type that holds it.struct Word<'a> { text: &'a str,}struct Line<'a> { words: Vec<Word<'a>>,}
// Owning: the text is copied once, and no lifetime appears anywhere.struct OwnedWord { text: String,}struct OwnedLine { words: Vec<OwnedWord>,}
fn borrowed_line(raw: &str) -> Line<'_> { Line { words: raw.split(' ').map(|text| Word { text }).collect(), }}
fn owned_line(raw: &str) -> OwnedLine { OwnedLine { words: raw .split(' ') .map(|text| OwnedWord { text: text.to_string(), }) .collect(), }}
fn main() { let owned; { let raw = String::from("own the data"); let borrowed = borrowed_line(&raw); println!("borrowed, last word: {}", borrowed.words[2].text); owned = owned_line(&raw); } // `raw` is gone: `borrowed` could not be used here, `owned` can println!("owned, last word: {}", owned.words[2].text); println!("size_of::<Word>(): {}", size_of::<Word>()); println!("size_of::<OwnedWord>(): {}", size_of::<OwnedWord>());}borrowed, last word: data owned, last word: data size_of::<Word>(): 16 size_of::<OwnedWord>(): 24
Line<'a> could only be used while raw was alive. OwnedLine outlived raw, and could go into a Vec, be returned from any function, or move to another thread. (map builds each word and collect gathers them into a list; Iterators covers both.) On a 64-bit target the borrowed Word is 16 bytes and the owned one 24, and the owned one needed a copy of every word.
Borrowing is not automatically the better choice. Borrow in a struct when it is a short-lived view over data you already hold, such as a parser or an iterator over one buffer. Own when the struct is stored, returned, sent to a thread, or kept in a collection. When a lifetime error blocks you, .to_string() or .clone() at the boundary is a fine first step.
Going deeper
Lifetime bounds
'b: 'a says 'b lasts at least as long as 'a. T: 'a is the same idea for a type: all the borrows inside T last at least as long as 'a. A signature is a promise the body must keep: the compiler checks callers against the signature alone, and the body against the signature. A body that returns something the signature did not promise is an error:
fn pick<'a, 'b>(x: &'a str, y: &'b str, first: bool) -> &'a str { if first { x } else { y }}
fn main() { println!("pick: {}", pick("x", "y", false));}error: lifetime may not live long enough --> src/main.rs:2:27 | 1 | fn pick<'a, 'b>(x: &'a str, y: &'b str, first: bool) -> &'a str { | -- -- lifetime `'b` defined here | | | lifetime `'a` defined here 2 | if first { x } else { y } | ^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b` | = help: consider adding the following bound: `'b: 'a`
The help: names the bound: a &'b str may be returned as a &'a str only if 'b outlives 'a.
// 'b: 'a reads "'b outlives 'a". It lets a &'b str be returned as a &'a str.fn pick<'a, 'b: 'a>(x: &'a str, y: &'b str, first: bool) -> &'a str { if first { x } else { y }}
// The compiler infers T: 'a here; you do not write it.struct Holder<'a, T> { value: &'a T,}
fn main() { let x = String::from("x"); let y = String::from("y"); println!("pick: {}", pick(&x, &y, false)); let holder = Holder { value: &x }; println!("holder: {}", holder.value);}pick: y holder: x
Holder<'a, T> did not need T: 'a written. The compiler works it out from the field &'a T.
Two lifetimes in one struct
One 'a is enough for most structs. A struct with two references can give them separate lifetimes when they end at different times and a method returns only one of them:
// Two lifetimes: the text and the query can end at different times.struct Search<'t, 'q> { text: &'t str, query: &'q str,}
impl<'t, 'q> Search<'t, 'q> { // The match is a piece of the text, so it carries 't and not 'q. fn first_match(&self) -> Option<&'t str> { let at = self.text.find(self.query)?; Some(&self.text[at..at + self.query.len()]) }}
fn main() { let text = String::from("lifetimes are regions"); let found; { let query = String::from("are"); let search = Search { text: &text, query: &query, }; found = search.first_match(); } // `query` and `search` are gone, `found` is not println!("found: {found:?}");}found: Some("are")first_match returns a piece of the text, so its type says 't. query and search are dropped before found is printed, and that is allowed because found does not depend on 'q. With one lifetime for both fields the compiler must pick a stretch that fits both references, and the result is tied to the shorter one:
// One lifetime for both fields: the compiler must pick one that fits both.struct Search<'a> { text: &'a str, query: &'a str,}
impl<'a> Search<'a> { fn first_match(&self) -> Option<&'a str> { let at = self.text.find(self.query)?; Some(&self.text[at..at + self.query.len()]) }}
fn main() { let text = String::from("lifetimes are regions"); let found; { let query = String::from("are"); let search = Search { text: &text, query: &query, }; found = search.first_match(); } println!("found: {found:?}");}error[E0597]: `query` does not live long enough --> src/main.rs:21:20 | 18 | let query = String::from("are"); | ----- binding `query` declared here ... 21 | query: &query, | ^^^^^^ borrowed value does not live long enough ... 24 | } | - `query` dropped here while still borrowed 25 | println!("found: {found:?}"); | ----- borrow later used here
Start with one lifetime and split it when an error like this one shows that two inputs end at different times. It is the lesson of tail and skip again: give each reference only the links it really has.
Iterators that borrow
Iterator::next takes &mut self, but its result type, Self::Item, cannot mention that borrow. An iterator over borrowed text therefore has to give out items that borrow from the text itself:
struct Words<'a> { rest: &'a str,}
impl<'a> Iterator for Words<'a> { // Every item borrows from the text, not from the Words that found it. type Item = &'a str;
fn next(&mut self) -> Option<&'a str> { let rest = self.rest.trim_start(); if rest.is_empty() { return None; } let (word, tail) = rest.split_once(' ').unwrap_or((rest, "")); self.rest = tail; Some(word) }}
fn main() { let text = String::from("lifetimes are regions"); let words: Vec<&str> = Words { rest: &text }.collect(); println!("words: {words:?}");}words: ["lifetimes", "are", "regions"]
Items that borrowed from the iterator are rejected:
struct Words { text: String,}
impl Iterator for Words { type Item = &str; // would borrow from the Words that `next` was called on
fn next(&mut self) -> Option<&str> { self.text.split(' ').next() }}
fn main() { let words = Words { text: String::from("lifetimes are regions"), }; for word in words { println!("word: {word}"); }}error: associated type `Iterator::Item` is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type --> src/main.rs:6:17 | 6 | type Item = &str; // would borrow from the Words that `next` was called on | ^ | note: you can't create an `Iterator` that borrows each `Item` from itself, but you can instead create a new type that borrows your existing type and implement `Iterator` for that new type --> src/main.rs:5:19 | 5 | impl Iterator for Words { | ^^^^^
Where a borrow really ends
The borrow checker’s current design, non-lexical lifetimes (NLL), ends a borrow at the last use of the reference, not at the end of its block (Borrowing has the run). One exception matters once structs hold references: if the value holding the borrow has a Drop impl, the drop at the end of its scope counts as a use.
struct Guard<'a>(&'a mut Vec<i32>);
impl Drop for Guard<'_> { fn drop(&mut self) { println!("guard dropped"); }}
fn main() { let mut v = vec![1]; let guard = Guard(&mut v); println!("last use of guard: {}", guard.0.len()); v.push(2); // `guard` is not used again, but its drop runs at the end of `main`}error[E0499]: cannot borrow `v` as mutable more than once at a time --> src/main.rs:13:5 | 11 | let guard = Guard(&mut v); | ------ first mutable borrow occurs here 12 | println!("last use of guard: {}", guard.0.len()); 13 | v.push(2); // `guard` is not used again, but its drop runs at the end of `main` | ^ second mutable borrow occurs here 14 | } | - first borrow might be used here, when `guard` is dropped and runs the `Drop` code for type `Guard`
Without the impl Drop the same program compiles.
The checker is also not all-knowing. This function is fine at run time, but is rejected:
use std::collections::HashMap;
fn get_or_insert(map: &mut HashMap<u32, String>, key: u32) -> &String { if let Some(value) = map.get(&key) { return value; } map.insert(key, String::from("new")); &map[&key]}
fn main() { let mut map = HashMap::new(); println!("value: {}", get_or_insert(&mut map, 1));}error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable --> src/main.rs:7:5 | 3 | fn get_or_insert(map: &mut HashMap<u32, String>, key: u32) -> &String { | - let's call the lifetime of this reference `'1` 4 | if let Some(value) = map.get(&key) { | --- immutable borrow occurs here 5 | return value; | ----- returning this value requires that `*map` is borrowed for `'1` 6 | } 7 | map.insert(key, String::from("new")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
One path returns value, so for the caller the borrow of *map must last as long as the result, and the compiler applies that to the path that did not return it. The NLL design calls this “problem case #3”, and the compiler used here still rejects it. The usual fix is to give the compiler a shape it accepts, here the entry API from Maps:
use std::collections::HashMap;
fn get_or_insert(map: &mut HashMap<u32, String>, key: u32) -> &String { map.entry(key).or_insert_with(|| String::from("new"))}
fn main() { let mut map = HashMap::new(); println!("first call: {}", get_or_insert(&mut map, 1)); println!("second call: {}", get_or_insert(&mut map, 1)); println!("entries: {}", map.len());}first call: new second call: new entries: 1
If you cannot restructure, own the data, or return an index or key and look the value up again.
Practice
Try it
Each exercise is a small program with checks in main. Run it in the Playground until it prints all checks passed. The starters return copies so that they compile; you change them to borrow, and the checks can tell a borrow from a copy by the address.
First and last piece
Return the first and last non-empty piece of text between separators sep. Change the signature so the pieces borrow from text. The compiler’s help: for E0106 offers a 'a on every reference. Try it, read the E0597 that main then reports, and fix the signature. The checks include a leading separator, no pieces, one piece, and a separator that is more than one byte.
/// The first and the last non-empty piece of `text` between separators `sep`, or `None`/// when there is no piece. Both pieces must be views into `text`: no copies.////// The starter returns copies (`String`) so that it compiles. Change the signature so/// the pieces borrow from `text`.fn first_and_last(text: &str, sep: &str) -> Option<(String, String)> { todo!("text.split(sep), filter out empty pieces, next() and last(); then fix the types")}
/// Do the bytes of `piece` lie inside the bytes of `text`? A copy would not.fn inside(piece: &str, text: &str) -> bool { let start = text.as_ptr() as usize; let at = piece.as_ptr() as usize; at >= start && at + piece.len() <= start + text.len()}
fn main() { let text = String::from("alpha,beta,,gamma,"); // `sep` is gone before the pieces are used, so they must not borrow from it. let found; { let sep = String::from(","); found = first_and_last(&text, &sep); } let (first, last) = found.expect("there are pieces"); assert_eq!(first, "alpha"); assert_eq!(last, "gamma"); assert!(inside(&*first, &text)); assert!(inside(&*last, &text));
// One piece is both the first and the last. let (first, last) = first_and_last("solo", ",").unwrap(); assert_eq!(first, "solo"); assert_eq!(last, "solo");
// Leading, trailing and doubled separators make empty pieces; they do not count. let (first, last) = first_and_last(",a,,b,", ",").unwrap(); assert_eq!(first, "a"); assert_eq!(last, "b");
// Nothing to return. assert_eq!(first_and_last("", ","), None); assert_eq!(first_and_last(",,,", ","), None);
// Not English: a multi-byte separator and accented text. let text = String::from("zürich→bern→genève"); let (first, last) = first_and_last(&text, "→").unwrap(); assert_eq!(first, "zürich"); assert_eq!(last, "genève"); assert!(inside(&*last, &text));
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// The first and the last non-empty piece of `text` between separators `sep`, or `None`/// when there is no piece. Both pieces must be views into `text`: no copies.////// `sep` has its own (unnamed) lifetime, so the caller may drop it right away.fn first_and_last<'a>(text: &'a str, sep: &str) -> Option<(&'a str, &'a str)> { let mut pieces = text.split(sep).filter(|piece| !piece.is_empty()); let first = pieces.next()?; let last = pieces.last().unwrap_or(first); Some((first, last))}
/// Do the bytes of `piece` lie inside the bytes of `text`? A copy would not.fn inside(piece: &str, text: &str) -> bool { let start = text.as_ptr() as usize; let at = piece.as_ptr() as usize; at >= start && at + piece.len() <= start + text.len()}
fn main() { let text = String::from("alpha,beta,,gamma,"); // `sep` is gone before the pieces are used, so they must not borrow from it. let found; { let sep = String::from(","); found = first_and_last(&text, &sep); } let (first, last) = found.expect("there are pieces"); assert_eq!(first, "alpha"); assert_eq!(last, "gamma"); assert!(inside(&*first, &text)); assert!(inside(&*last, &text));
// One piece is both the first and the last. let (first, last) = first_and_last("solo", ",").unwrap(); assert_eq!(first, "solo"); assert_eq!(last, "solo");
// Leading, trailing and doubled separators make empty pieces; they do not count. let (first, last) = first_and_last(",a,,b,", ",").unwrap(); assert_eq!(first, "a"); assert_eq!(last, "b");
// Nothing to return. assert_eq!(first_and_last("", ","), None); assert_eq!(first_and_last(",,,", ","), None);
// Not English: a multi-byte separator and accented text. let text = String::from("zürich→bern→genève"); let (first, last) = first_and_last(&text, "→").unwrap(); assert_eq!(first, "zürich"); assert_eq!(last, "genève"); assert!(inside(&*last, &text));
println!("all checks passed");}all checks passed
Borrow a section
Section owns two Strings. Give it a lifetime and make parse and first_word borrow from the document. first_word must return &'a str, not a &str tied to the section: the checks drop the section before they use the word. Title and body are the text as it is, with nothing trimmed.
/// A document section: a title line, then the body.////// The starter owns its text, so it compiles. Make it borrow from the document instead:/// give `Section` a lifetime, and have `first_word` return a `&str` that points into the/// document and does not borrow from the `Section` itself.struct Section { title: String, body: String,}
impl Section { /// "Title\nthe rest" -> the title is the first line, the body is everything after /// the first newline. No newline: no section. fn parse(document: &str) -> Option<Section> { todo!("split_once('\\n')") }
/// The first whitespace-separated word of the body, or "" when there is none. fn first_word(&self) -> String { todo!("split_whitespace") }}
/// Do the bytes of `piece` lie inside the bytes of `text`? A copy would not.fn inside(piece: &str, text: &str) -> bool { let start = text.as_ptr() as usize; let at = piece.as_ptr() as usize; at >= start && at + piece.len() <= start + text.len()}
fn main() { let document = String::from("Lifetimes\nare regions of the program\nnot numbers"); let section = Section::parse(&document).expect("has a title line"); assert_eq!(section.title, "Lifetimes"); assert_eq!(section.body, "are regions of the program\nnot numbers"); assert!(inside(&*section.title, &document)); assert!(inside(&*section.body, &document));
// The word points into the document, so it can outlive the section. let word = section.first_word(); drop(section); assert_eq!(word, "are"); assert!(inside(&*word, &document));
// No newline, no section. assert!(Section::parse("just a title").is_none());
// An empty body has no first word. let document = String::from("Title\n"); let section = Section::parse(&document).unwrap(); assert_eq!(section.first_word(), "");
// Not English, and nothing is trimmed: title and body are exactly the text. let document = String::from("Zürich\n über den Wolken"); let section = Section::parse(&document).unwrap(); assert_eq!(section.title, "Zürich"); assert_eq!(section.body, " über den Wolken"); assert_eq!(section.first_word(), "über"); let section = Section::parse(" padded \nx ").unwrap(); assert_eq!(section.title, " padded "); assert_eq!(section.body, "x ");
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// A document section: a title line, then the body. It borrows both from the document.struct Section<'a> { title: &'a str, body: &'a str,}
impl<'a> Section<'a> { /// "Title\nthe rest" -> the title is the first line, the body is everything after /// the first newline. No newline: no section. fn parse(document: &'a str) -> Option<Section<'a>> { let (title, body) = document.split_once('\n')?; Some(Section { title, body }) }
/// The first whitespace-separated word of the body, or "" when there is none. /// It borrows from the document (`'a`), not from the `Section` that found it. fn first_word(&self) -> &'a str { self.body.split_whitespace().next().unwrap_or("") }}
/// Do the bytes of `piece` lie inside the bytes of `text`? A copy would not.fn inside(piece: &str, text: &str) -> bool { let start = text.as_ptr() as usize; let at = piece.as_ptr() as usize; at >= start && at + piece.len() <= start + text.len()}
fn main() { let document = String::from("Lifetimes\nare regions of the program\nnot numbers"); let section = Section::parse(&document).expect("has a title line"); assert_eq!(section.title, "Lifetimes"); assert_eq!(section.body, "are regions of the program\nnot numbers"); assert!(inside(&*section.title, &document)); assert!(inside(&*section.body, &document));
// The word points into the document, so it can outlive the section. let word = section.first_word(); drop(section); assert_eq!(word, "are"); assert!(inside(&*word, &document));
// No newline, no section. assert!(Section::parse("just a title").is_none());
// An empty body has no first word. let document = String::from("Title\n"); let section = Section::parse(&document).unwrap(); assert_eq!(section.first_word(), "");
// Not English, and nothing is trimmed: title and body are exactly the text. let document = String::from("Zürich\n über den Wolken"); let section = Section::parse(&document).unwrap(); assert_eq!(section.title, "Zürich"); assert_eq!(section.body, " über den Wolken"); assert_eq!(section.first_word(), "über"); let section = Section::parse(" padded \nx ").unwrap(); assert_eq!(section.title, " padded "); assert_eq!(section.body, "x ");
println!("all checks passed");}all checks passed
An iterator over borrowed words
Finish Words, an iterator over the words of a text. The starter yields Strings; change Item so that every word is a view into the text and still valid after the iterator is gone. The checks include runs of whitespace, a trailing space and a multi-byte space.
/// An iterator over the whitespace-separated words of a text.////// The starter yields owned `String`s, so it compiles. A view into the text is cheaper:/// change the iterator so each word is a `&str` into the text.struct Words<'a> { rest: &'a str,}
impl<'a> Words<'a> { fn new(text: &'a str) -> Words<'a> { Words { rest: text } }}
impl Iterator for Words<'_> { type Item = String;
fn next(&mut self) -> Option<String> { todo!("skip leading whitespace, take up to the next whitespace, keep the rest") }}
/// Do the bytes of `piece` lie inside the bytes of `text`? A copy would not.fn inside(piece: &str, text: &str) -> bool { let start = text.as_ptr() as usize; let at = piece.as_ptr() as usize; at >= start && at + piece.len() <= start + text.len()}
fn main() { let text = String::from(" alpha beta\tgamma\n"); let words: Vec<_> = Words::new(&text).collect(); assert_eq!(words, ["alpha", "beta", "gamma"]); for word in &words { assert!(inside(&*word, &text)); }
// The words outlive the iterator that found them. let first; { let mut iter = Words::new(&text); first = iter.next(); } assert_eq!(first.as_deref(), Some("alpha"));
// Trailing whitespace does not make an empty word. assert_eq!(Words::new("a b ").count(), 2);
// Nothing to find. assert_eq!(Words::new("").next(), None); assert_eq!(Words::new(" \t\n").next(), None);
// Not English: a multi-byte word and a multi-byte space (U+3000). let text = String::from("zürich\u{3000}bern"); let words: Vec<_> = Words::new(&text).collect(); assert_eq!(words, ["zürich", "bern"]); assert!(inside(&*words[0], &text));
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// An iterator over the whitespace-separated words of a text. Every word is a view into/// the text, so it can outlive the iterator that found it.struct Words<'a> { rest: &'a str,}
impl<'a> Words<'a> { fn new(text: &'a str) -> Words<'a> { Words { rest: text } }}
impl<'a> Iterator for Words<'a> { // The item names `'a`, the text; it cannot name the borrow of `&mut self`. type Item = &'a str;
fn next(&mut self) -> Option<&'a str> { let rest = self.rest.trim_start(); if rest.is_empty() { return None; } let end = rest.find(char::is_whitespace).unwrap_or(rest.len()); let (word, tail) = rest.split_at(end); self.rest = tail; Some(word) }}
/// Do the bytes of `piece` lie inside the bytes of `text`? A copy would not.fn inside(piece: &str, text: &str) -> bool { let start = text.as_ptr() as usize; let at = piece.as_ptr() as usize; at >= start && at + piece.len() <= start + text.len()}
fn main() { let text = String::from(" alpha beta\tgamma\n"); let words: Vec<_> = Words::new(&text).collect(); assert_eq!(words, ["alpha", "beta", "gamma"]); for word in &words { assert!(inside(&*word, &text)); }
// The words outlive the iterator that found them. let first; { let mut iter = Words::new(&text); first = iter.next(); } assert_eq!(first.as_deref(), Some("alpha"));
// Trailing whitespace does not make an empty word. assert_eq!(Words::new("a b ").count(), 2);
// Nothing to find. assert_eq!(Words::new("").next(), None); assert_eq!(Words::new(" \t\n").next(), None);
// Not English: a multi-byte word and a multi-byte space (U+3000). let text = String::from("zürich\u{3000}bern"); let words: Vec<_> = Words::new(&text).collect(); assert_eq!(words, ["zürich", "bern"]); assert!(inside(&*words[0], &text));
println!("all checks passed");}all checks passed
Read more
- The Rust Book: Validating References with Lifetimes, which also covers
'static. - Rust by Example: Lifetimes, with pages on elision, structs and
'static. - The Reference: Lifetime elision and Trait and lifetime bounds.
- The Rustonomicon: Lifetimes, for a deeper look at how the compiler reasons.
- Error index: E0106, E0597 and E0716, or this site’s index.
- Reading signatures for
'aamong the other symbols in a signature.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.