What’s the simplest way to understand lifetimes without getting stuck on the syntax?
So every time you borrow something, there's a lifetime - it's just elided from your display. Long ago, you'd be writing Rust like this:
#![allow(unused)] fn main() { fn inspect(thing: &'thing_lifetime Thing) }
With the ugly 'lifetime_name everywhere. The compiler keeps reducing the frequency with which you actually need to specify the lifetime!
Let's take a function:
#![allow(unused)] fn main() { fn compare(a: &Thing, b: &Thing) -> &Thing }
This won't compile, because Rust won't try and guess the lifetime soup. So you need to help it out. In this case, you want to compare a and b and return whichever reference meets the criteria. So it stands to reason that while the function is around, a and b must still exist - and the returned reference must still exist (in single-threaded code, that's going to be true). So you help Rust out:
#![allow(unused)] fn main() { fn compare(a: &'a Thing, b: &'a Thing) -> &'a Thing }
We have just the one lifetime - because all the things share the lifetime.
Likewise, if you had a struct that holds a reference:
#![allow(unused)] fn main() { struct MyIndex<'a> { target: &'a Document } }
You have to specify a lifetime. What's the 'a really doing?
- It's naming the lifetime
a(you can use longer names!) - It's saying that for this structure to remain valid, the Document it points to must still exist.
Lifetimes are a little complicated. There's "lifetime extension", and cases in which the compiler is able to say "well, that reference is never used again so I can drop it here". In general: lifetimes are there to stop you using a reference after the thing it points to has stopped being there.
And the glib answer: Rust programmers use references less frequently than C++ programmers. I sometimes find myself thinking "oh, it wants a lifetime specifier? Have I overcomplicated things?".