Can someone point me to a resource explaning clearly how to use lifetimes with struct. Especially when you have two references in the struct when to use only 'a and when to use 'a and 'b with an example demonstrating that it's working with one and not with the other
You'll rarely need more than one lifetime parameter.
You'd only need one, if two members of the struct are related in terms of lifetime, i.e. when one must outlive the other.
Usually one lifetime parameter will suffice, since it will be evaluated to the longest possible common lifetime of all members using it.
Otherwise the one or more references would be invalid during the struct's lifetime, which is not allowed by the Rust compiler.
Off the top of my head I cannot think of an example where you'd really need two distinct lifetime parameters in a struct.
Generally, a struct with a lifetime is used when the struct is used to look inside another struct. For example, if you have a HashMap
, then there's a type called OccupiedEntry
that represents a location in the map and lets you make modifications to that entry. E.g., you can call get
to view the value, or you can call remove
to remove it. Since the entry has a lifetime, the value is not stored inside the entry, but the entry instead looks inside the HashMap
and makes modifications to the map.
As for the lifetime, the 'a
in OccupiedEntry<'a, K, V>
represents the duration in which the entry is borrowing the hash map. I.e., if you have such an entry, the map is mutably borrowed for (at least) the duration 'a
, and attempts to access the map directly during 'a
will fail with an error because it's already borrowed. The entry itself can never be used outside of 'a
.
Another common use-case of lifetimes on structs is iterators. An iterator on a Vec<T>
is used to look inside the vector, and the iterator itself doesn't actually store the values being iterated; those are stored in the vector instead.
Never.
#[cfg(one_lifetime)]
struct Foo<'a> {
x: &'a String,
y: &'a String,
}
#[cfg(two_lifetimes)]
struct Foo<'a, 'b> {
x: &'a String,
y: &'b String,
}
fn main() {
let x_unpacked;
let y_unpacked;
let foo: Foo;
{
let x = &String::from("word");
{
let y = &String::from("hello");
{
// Pack into struct
foo = Foo { x, y };
x_unpacked = foo.x;
y_unpacked = foo.y;
}
println!("{y_unpacked}");
}
println!("{x_unpacked}");
}
}
It's easy to see why two lifetimes work and one doesn't work here.
I guess such resource doesn't exist because answer is so obvious: two lifetimes are strictly more flexible than one, which means that you only use one when you have to (codegenerator that can only use one lifetime or something).
How often do you use scope-dependent unpacking of references in the real world, though.
And regarding "never". Even the standard library does it in the linked OccupiedEntry
.
Even in that case it's better to declare struct with two lifetimes and then only use one in the impl
block. You can save some typing if that's an internal struct that you may never need to change, but it's hard to predict. Especially the future.
Yes, but that only works if you can guarantee that such lifetime exists. Sometimes it doesn't even work in such cases, but if you really need two lifetimes (as in my cases) then it couldn't work.
Any struct that provides references to something (that is: when reference can be taken out of struct and passed somewhere else).
You may only be sure that one lifetime is enough if none of functions that accept your struct return references found in it and only ever use the full struct. And we are back to the square one: it's hard to predict. Especially about the future.
How often do you have structs with more than one lifetime?
And it's not about “scope-dependent unpacking”, it's literally every time when you pass thing as one struct to some function and then use one of these two values independently.
This doesn't happen often, but when it does happen the refactoring is usually horrendous and involves hundreds, if not thousands lines of code.
Alice's answer is very detailed except she haven't told the main thing: that's explanation about why it's, usually, an anti-pattern to have more than one or two lifetimes in a struct. You rarely even need structs with one lifetime and two lifetimes are ever rarer, but when you have dozen lifetimes, for some reason (and the temptation to “save in typing” is highest) then chances that one or another will need to outlive the rest are growing pretty quickly.
Exactly, that was what I thought, but why isn't there lifetime elision on structs then if it is that obvious. So I keep thinking I am missing something here.
Explaining lifetimes on functions is working out fine, you can explain when and when not to use multiple lifetime paramaters and show an example where you have to use more than one to make it compile. I am looking for a simalar line of reasoning with structs
Thank you for your response, I think i can follow, but still I would like have a clear example, as small as possible, where you have to use one or the other. In your uses cases there is only one reference if I am correct my question is about a struct with two references when to uses 'a 'a and when to use 'a 'b.
Because it wouldn't be useful.
Lifetimes on functions are local: no one outside of your function have to ever specify them.
Lifetimes of structs are global: as @conqp correctly noted you may want to restrict these lifetimes later.
When you do that it's important to know which lifetime corresponds to which field and if there are more than one the answer is not obvious.
It could have been helpful to “save on typing” to have an elision for the “one lifetime mark only” use-case, but even then it's not a very bright idea: non-owning structs are rare and that's why you, usually, want to know if your struct includes explicit lifetime reference or not. You treat it differently if it's not owning struct.
The important difference with functions is that sometimes you have to have two different lifetimes to make function compile, but sometime it's the opposite: you need just one and two wouldn't work.
With structs it's very different (but simpler) story: two would always work, where one does, but the opposite is not true, it's not really possible to create an example where one lifetime works, and two don't work (except for very pathological cases with code generation, etc). Hence the conclusion: always use separate lifetime, except if you know you would never need it.
Well, it's literally one line: separate lifetimes work where single one wouldn't work, but not the other way around. And that's it. Really, there are nothing else to discuss.