Not really. Most tutorials avoid it like a plague – and for good reason, too!
In an ideal world the key phrase “this is math” wouldn't send people running for the hills. And if you are actually allowed to utter that phrase then everything is very simple and you don't even need that magic “aha” moment.
Alas, we don't live in such a world and after many years in school aversion to math is brought to the level that “this is math” phrase leads not to the enlightenment and “aha” moment but to internal debate about whether it's appropriate to “run for the hills” pronto… or ex post pronto.
Writers of Rust tutorials are, rightfully, try to explain things without uttering that “awful” phrase, but it works approximately as well as trying to teach music without being able to utter words “sound” or “pitch”.
Lifetimes were never problematic to me and I suspect that anyone who claims they “couldn't understand” lifetimes is just looking on the absolutely clear and simple explanations while attempting to suppress these explanations and ignore them (as much as possible, anyway).
Ultimately the question that matters is not “what are lifetimes” but rather “what are lifetime annotations”.
And if you accept “this is math” thingie then they are not magical at all. They are similar to these α, β, γ, δ, ε marks that you saw in school when they tried to teach geometry to you (but managed only to cultivate [un]healthy loathing of that subject).
Rust compiler includes is a theorem prover that is there to ensure that your program is “correct” (for some definition of “correct”). After said theorem prover says “Ok, this program is correct, it can be turned into the machine code” program is compiled while mostly ignoring the lifetimes and lifetime annotations.
That's why “make compiler happy by blindly following the instructions in the error messages” works so well: because lifetimes are, technically, not part of your program, but more of “an attached machine-checkable documentation” about you program it doesn't actually matter if that description is correct or not. If compiler is happy, then code works and you happy, too! Even if annotations are all wrong.
And now, when we know what lifetime markups are it's easy to explain other things, too. Including that dreaded covariance/contravariance/invariance thingie.
It's directly related to shared (means: & T
) and exclusive (means: &mut T
) borrows.
Because exclusive borrows are, well… exclusive – compiler have to pick one specific lifetime for every &'a mut T
variable in it's automatically-created proof of program correctness. The exact way it's done is complicated, but the idea is simple: we need to invent one lifetime for all cases where it's used.
That is known as “invariance”. Easy, right?
Because shared borrows are, well… shared – compiler can be more relaxed and is allowed to “imagine” that every shared reference carries not one lifetime markup but many lifetime markups. So when you write &b T
there are lifetimes b₁
, b₂
, b₃
, b₄
, b₅
, b₆
, b
… as many of them as needed: these are shared references, we can make as many of them as needed… so we may as well imagine that we are passing not one reference with one lifetime but lots of references (and with identical bit pattern in memory) with many lifetimes around.
Again: the exact method of picking these lifetimes could be complicated, but the important thing that every shared reference carries many of them. As many as needed.
This gives us covariance and contravariance: that imaginary “endless pile of imaginary shared references” with different lifetimes becomes “a covariant type” and functions that are ready to accept said “endless pile of imaginary shared references” with different lifetimes becomes “a contravariant type”. Not as easy and invariant types, but still… no mystery.
And that's it. Not much to talk about, really. If you are allowed to tell “how math works” with lifetimes then it's a very handy and easy to understand tool… but in school most of us are just taught to remember lots of scary words without any understanding about concepts that are behind these words… and as a result “this is math“ phrase is a big taboo for the Rust tutorials which makes some pieces of Rust very cryptic to explain.
But they are not intrinsically complex on scary, they are just made that way with our ways of teaching math in schools.