My learning process for rust lifetimes looked like this (based on the rust book):
- I want to annotate, when values behind references go out of scope
- Usually (not always! see .data section, i.e. 'static) values live within a
{}
block - We annotate blocks like
't: {…}
and e.g. struct fields get a lifetime like&'t ident
with the same lifetime namet
- This understanding is wrong. Why? The block name definitions are most likely unknown to the struct implementor and there might be several block name definitions for the same struct.
- So the definition
't: {…}
and usage&'t ident
must be completely independent. - Compilers can easily determine definitions, thus users never have to write
't: {…}
. Programmers only need to care about the&'t ident
specification part. - Compilers could analyze function bodies (in case of
struct
: use of the struct members) and determine the&'t ident
part. - This understanding is wrong. Why? Because sometimes the function body (or use of struct members) is not yet available (e.g. a trait specifies a function, but the implementation is done by some other party in the future).
- As a result,
struct
andfn
must fully specify lifetimes in their struct definition or function signature, respectively. - Specifications mostly follow the same heuristic rules. So we introduce lifetime elision. It inserts lifetimes based on rules targeting the most common usecases and we can opt-out anytime.
At this point, I think my understanding is pretty close to how it actually works. But now, my understanding gets wrong. Let us look at some example:
#[derive(Debug)]
struct Stats {
league: &str,
}
const NAME: &str = "rust";
fn more_difficult_league(s1: &Stats, s2: &Stats) -> &str {
if s1.league == s2.league {
s1.league
} else if s1.league == "PHP" {
s2.league
} else {
"C++"
}
}
fn main() {
let mut st = Stats { league: name };
let dleague = more_difficult_league(&st, &st);
println!("{}", dleague);
}
Obviously, I omitted any lifetime specifications.
The lifetime of struct fields is either the entire duration of the program (
'static
) or as long as the struct (Stats<'a>
withleague: &'a str
)In a function/method, we might get references with lifetimes
'a
,'b
,'c
, …. What is the return value's lifetime?- Either it is some static value (
'static
) - Either it is always the same specific lifetime (like
'c
) - Either it is one specific lifetime - which one will be known at compile or run time. For the compiler we must specify the worst case lifetime
max('a, 'b, 'c, …)
. To the best of my knowledge this can be done by giving every reference the same lifetime.
- Either it is some static value (
This seems to work for the following contrived, shorter function:
fn more_difficult_league<'a>(s1: &'a Stats, s2: &'a Stats) -> &'a str {
if s1.league == s2.league {
s1.league
} else {
s2.league
}
}
If we add some 'static
return value, the worst case lifetime is max('a, 'static)
which is presumably 'static
:
fn more_difficult_league<'a>(s1: &'a Stats, s2: &'a Stats) -> &'static str {
if s1.league == s2.league {
s1.league
} else if s1.league == "PHP" {
s2.league
} else {
"C++"
}
}
This gives error[E0621]: explicit lifetime required in the type of s1
and lifetime 'static required
for s2.league
.
At which point is my understanding wrong? Thanks in advance for bearing with me.
Disclaimer: help: add explicit lifetime 'static to the type of s1: &'a Stats<'static>
would work here, but seems wrong to me.