Reading — step 1 of 7
Learn
Lifetimes
A reference must never outlive the value it points to. Lifetimes are how the borrow checker proves that. They don't make anything live longer — they describe relationships between references so the compiler can check them.
The bug lifetimes eliminate
let r;
{
let x = 5;
r = &x; // borrow x
} // x dropped here
println!("{}", r); // r would dangle
error[E0597]: `x` does not live long enough
In C, this compiles and reads freed stack memory. Rust compares the region where r is used against the region where x exists, and rejects the program because the reference wins.
Returning references from functions
Inside a function body the compiler sees everything. At a function boundary it only sees the signature — so a function returning a reference must declare where that reference could come from:
// error[E0106]: missing lifetime specifier
// (borrowed from `a` or from `b`? the signature must say)
fn longer(a: &str, b: &str) -> &str { /* ... */ }
The fix is a generic lifetime parameter:
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
Read 'a as: "some region of code where both inputs are valid; the result may only be used inside it." Concretely, the returned reference is usable only while the shorter-lived input is still alive — the compiler can't know which one you returned, so it assumes the worst:
let a = String::from("long string is long");
let result;
{
let b = String::from("short");
result = longer(&a, &b);
} // b dropped — result MIGHT point into b
println!("{}", result);
error[E0597]: `b` does not live long enough
Note what the annotation did NOT do: it didn't extend any lifetime. Annotations only restrict how long the caller may keep the result.
'static, and structs that borrow
'static means "valid for the whole program". String literals are baked into the binary, so:
let s: &'static str = "hello";
A struct can hold references, but then it needs a lifetime parameter too — the struct must not outlive what it borrows from:
struct Excerpt<'a> {
text: &'a str,
}
Why you rarely write lifetimes: elision
Three rules cover most signatures:
- Each input reference gets its own lifetime.
- If there is exactly one input lifetime, the output gets it.
- In methods, if
&selfis a parameter, the output getsself's lifetime.
That's why fn first_word(s: &str) -> &str needs no annotations. Only when multiple input references could be the source of a returned reference — exactly the longer case — does the compiler make you spell it out.
Your exercise
Implement fn longer<'a>(a: &'a str, b: &'a str) -> &'a str that returns whichever input is longer, and a when the lengths are equal — so compare with >=. Uncomment the println! in main (you can delete the two let _ = ...; placeholder lines); the starter already reads the two lines.
TEST 1 feeds hello then hi and expects exactly:
hello
Mistakes the grader catches:
- Comparing the strings instead of their lengths.
if a >= bcompares alphabetically, and "hi" sorts after "hello" — TEST 1 printshiand fails. Comparea.len() >= b.len(). - Returning an owned value like
a.to_string()— the signature promises&'a str; that'serror[E0308]: mismatched types. - Returning a reference to a local (e.g.
&format!("{}", a)) — the temporary dies at the end of the function:error[E0515]: cannot return reference to temporary value. Returnaorbthemselves — that's the whole point of the shared'a.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…