Skip to content
Learn Rust
Theme

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

Rustsrc/main.rsRun ↗
// '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());
}
$ cargo run
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))
}
$ go run main.go
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}")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
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))
$ python3 main.py
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);
$ tsc --strict --module nodenext --types node main.ts && node main.js
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());
}
}
$ javac Main.java && java Main
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);
$ dotnet run
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;
}
$ gcc -Wall -Wextra main.c -o main && ./main
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
longest: Call me Ishmael
excerpt length: 15

What's different

  • 'a is a name. longest<'a>(a: &'a str, b: &'a str) -> &'a str says: the result borrows from a and from b, so it is valid only while both are. The signature describes the link; the compiler checks it. Writing 'a never 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 'a when two or more inputs could be the source, or when a struct holds a reference.
  • struct Excerpt<'a> { part: &'a str } says an Excerpt cannot outlive the text its part points into. A struct that owns its data (String, Vec) has no lifetime to write, which is why the Structs lesson had none.
  • 'static is the longest lifetime: string literals have it, and T: 'static means "T holds 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.
Gotcha

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.
Rustsrc/main.rs✗ won't compile
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)
}
$ go run main.go✓ runs
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")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
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)
$ python3 main.py✓ runs
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);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
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);
}
}
$ javac Main.java && java Main✓ runs
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}");
$ dotnet run✓ runs
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;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
longest: a long first string
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0597?
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
What happens in Go

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.

What happens in Kotlin

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.

What happens in Python

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.

What happens in TypeScript

Prints both lines. s3 is out of scope after the block, but its string stays alive while other can reach it.

What happens in Java

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.

What happens in C#

C# runs this and prints both lines. The string from s3 stays alive while other can reach it, whatever happened to the variable.

What happens in C

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).

What happens in C++

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).

What Rust sees

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.

Why Rust says no

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.

Rust, fixedsrc/main.rsRun ↗
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}");
}
$ cargo run
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, as thread::spawn and Box<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 &.

Rustsrc/main.rsRun ↗
// '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}");
}
$ cargo run
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
Rustsrc/main.rsRun ↗
// 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"));
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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}");
}
$ cargo buildWhat is E0597?
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:

Rustsrc/main.rs✗ won't compile
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"));
}
$ cargo build
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:

Rustsrc/main.rsRun ↗
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>());
}
$ cargo run
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
let text = String::from("Call me Ishmael. Some years ago"); let excerpt = Excerpt { part };
STACKHEAPtext: Stringptrlen 31cap 31excerpt: Excerpt<'a>ptrlen 15part: an address and a length. 'a is not stored31 bytes, owned by textCall me Ishmael. Some years ago
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:

Rustsrc/main.rs✗ won't compile
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);
}
$ cargo buildWhat is E0597?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
a: Some("lifetimes")
b: Some("are")
c: Some("regions")
d: None

The 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:

Rustsrc/main.rs✗ won't compile
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:?}");
}
$ cargo buildWhat is E0499?
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.

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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));
}
$ cargo buildWhat is E0106?
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.

Rustsrc/main.rsRun ↗
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");
}
$ cargo run
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: “T holds no borrow that could end early”. An owned String satisfies it. It does not mean the value lives forever: Noisy was dropped when consume returned. A reference to a local does not satisfy it:
Rustsrc/main.rs✗ won't compile
fn consume<T: 'static>(_value: T) {}
fn main() {
let name = String::from("local");
consume(&name);
}
$ cargo buildWhat is E0597?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
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);
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
fn main() {
let word = String::from("temporary").as_str();
println!("word: {word}");
}
$ cargo buildWhat is E0716?
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.

Rustsrc/main.rsRun ↗
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>());
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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));
}
$ cargo build
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.

Rustsrc/main.rsRun ↗
// '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);
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
// 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:?}");
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
// 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:?}");
}
$ cargo buildWhat is E0597?
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:

Rustsrc/main.rsRun ↗
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:?}");
}
$ cargo run
words: ["lifetimes", "are", "regions"]

Items that borrowed from the iterator are rejected:

Rustsrc/main.rs✗ won't compile
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}");
}
}
$ cargo build
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.

Rustsrc/main.rs✗ won't compile
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`
}
$ cargo buildWhat is E0499?
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:

Rustsrc/main.rs✗ won't compile
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));
}
$ cargo buildWhat is E0502?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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.

Starterexercises/first-and-last.rs
/// 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
One solutionexercises/first-and-last.solution.rsRun ↗
/// 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");
}
$ cargo run
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.

Starterexercises/section.rs
/// 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
One solutionexercises/section.solution.rsRun ↗
/// 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");
}
$ cargo run
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.

Starterexercises/words.rs
/// 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
One solutionexercises/words.solution.rsRun ↗
/// 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");
}
$ cargo run
all checks passed

Read more