Step 1 of 7 · Reading · ~4 min
Learn
Lifetimes, Trait Objects, and Async
Lifetimes In Depth
By the end you can read signatures that carry several lifetimes or a bound such as 'b: 'a and T: 'static, and write a method that hands out data that outlives the borrow used to fetch it.
In Rust Intermediate you put 'a on functions and structs and met the elision rules. This lesson starts where one 'a stops being enough. Error texts are from rustc 1.40; newer ones say e.g. "lifetime may not live long enough" for the same code.
Two lifetimes and an outlives bound
When two inputs can flow into one result, the compiler must know how they relate. 'b: 'a reads "'b outlives 'a", so returning a &'b str where a &'a str is promised is safe:
fn either<'a, 'b: 'a>(x: &'a str, y: &'b str, first: bool) -> &'a str {
if first { x } else { y }
}
Drop the : 'a and rustc rejects the body (E0623, lifetime mismatch). T: 'a is the same idea for a type: every reference inside T lives at least 'a. It appears on structs holding a generic borrow, where T must stay valid for all of 'a:
struct Holder<'a, T: 'a> { item: &'a T }
T: 'static is the most misread bound: it does not demand a &'static, only that T holds no short-lived borrow. String, i32 and &'static str pass; &local does not:
fn keep<T: std::fmt::Debug + 'static>(t: T) { println!("{:?}", t); }
keep(owned.clone()); // ok
keep(&owned); // error[E0597]: `owned` does not live long enough
Hand out the inner lifetime
A struct that borrows text has two lifetimes: the borrow of the struct (&mut self) and the 'a of the text inside it. Elision rule 3 ties the result to the first:
struct Scanner<'a> { src: &'a str, pos: usize }
impl<'a> Scanner<'a> {
fn next_word(&mut self) -> Option<&str> { /* trim, find the space, slice src */ }
}
let mut sc = Scanner { src: &text, pos: 0 };
let a = sc.next_word();
let b = sc.next_word(); // error[E0499]: cannot borrow `sc` as mutable more than once
println!("{:?} {:?}", a, b);
The body compiles with either signature; only the return type's lifetime changes. Write -> Option<&'a str> and the result is a &'a str copied out of the struct. It borrows text, not sc, so a stays valid while sc advances.
Iterator forces this shape: type Item cannot name the lifetime of next's &mut self, so impl<'a> Iterator for Scanner<'a> { type Item = &'a str; ... } is the only form that compiles.
Borrowing iterators, closures, variance
An iterator you return must say it holds the borrow: + 'a means "valid for 'a", and + '_ means the same with the lifetime taken from the argument. Box<dyn Iterator<Item = &'a str> + 'a> takes the same suffix (dyn, "chosen at run time", has its own lesson):
fn long_words<'a>(s: &'a str, n: usize) -> impl Iterator<Item = &'a str> + 'a {
s.split_whitespace().filter(move |w| w.len() > n)
} // yields "quick", "brown" for ("a quick brown fox", 3)
F: Fn(&str) -> &str already means for<'x> Fn(&'x str) -> &'x str, read "for any lifetime 'x": the closure accepts whatever lifetime the caller picks at each call; the function may pass its own local String to f.
Variance is how a lifetime inside a type may change. &'a T is covariant: a longer-lived value can stand where a shorter-lived one is expected, so a &'static str works as a &'a str. &mut T and Cell<T> are invariant in T: the lifetime must match exactly, because you can write through them:
fn overwrite<'a>(slot: &mut &'a str, v: &'a str) { *slot = v; }
let mut s: &'static str = "x";
{
let t = String::from("y");
overwrite(&mut s, &t); // rejected: `t` does not live long enough
}
println!("{}", s); // would read freed memory
&mut &'static str cannot shrink to &mut &'a str, so 'a becomes 'static and &t fails.
A struct holding a raw pointer has no &'a field, yet is tied to 'a. PhantomData<&'a T> is a zero-size field that says so; without it rustc reports E0392 ('a never used):
struct View<'a, T> { ptr: *const T, len: usize, _tie: std::marker::PhantomData<&'a T> }
A struct referencing its own field cannot be written with any lifetime; the async-await lesson covers Pin, built for that.
Common mistakes
- Returning
Option<&str>fromnext_field. Elision ties it to&mut self; a second call while the first field is held fails with E0499. - Skipping the separator with
+ 1.éis 2 bytes and→is 3; slicing mid-char panics. Addself.sep.len_utf8(). - Treating an empty remainder as the end.
a,has two fields, not one.
Your exercise
Implement next_field, write the whole Iterator impl for the zero-copy Cursor (the starter has none), and use it in longest_field. main keeps earlier fields while pulling later ones.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…