It's actually a classic and much-repeated case in Rust education:
fn pick_first<'a>(x: &'a str, y: &'a str) -> &'a str {
x // We only actually return x, never y
}
fn main() {
let s1 = String::from("long-lived");
let result;
{
let s2 = String::from("short-lived");
result = pick_first(&s1, &s2);
} // s2 dropped here
println!("{}", result);
}
The error here is "borrowed value [pointing to &s2] does not live long enough". Of course it does live long enough, it's just that the constraints in the function signature don't say this usage is valid.
Thinking as a beginner, I think part of the problem here is the compiler is overstating its case. With experience, one learns to read this message as "borrowed value could not be proved to live as long as required by the function declaration", but that's not what it says! It asserts that the value in fact does not live long enough, which is clearly not true.
(Edit: having said this, I now realize the short version confuses beginners because of the definition of “enough”. They read it as “does not live long enough to be safe”, which the compiler is not—and cannot be—definitively saying.)
When this happens in a more complex situation (say, involving a deeper call tree and struct member lifetimes as well), you just get this same basic message, and finding the place where you've unnecessarily tied two lifetimes together can be a bit of a hunt.
My impression is that it's difficult or impossible for the compiler to "explain its reasoning" in a more complex case (I made an example at [0] [1]), which is understandable, but it does mean you always get this bare assertion "does not live long enough" and have to work through the tree of definitions yourself to find the bad constraint.
Would output along the following lines be an improvement?
error[E0597]: `buffer` does not live long enough
--> src/main.rs:63:21
|
59 | let report = {
| ------ borrow later stored here
60 | let mut buffer = Vec::new();
| ---------- binding `buffer` declared here
61 | let ctx = Context {
62 | config: &config,
| ------ this field and `buffer` are required by `Context` to have the same lifetime
63 | buffer: &mut buffer,
| ^^^^^^^^^^^ borrowed value does not live long enough
...
68 | };
| - `buffer` dropped here while still borrowed
help: consider making different fields in `Context` have independent lifetimes
|
4 | struct Context<'a> {
| ^^
5 | config: &'a Config,
| ^^
6 | buffer: &'a mut Vec<u8>,
| ^^
7 | }
It would be similar, pointing at the other argument. Neither case would have an applicable suggestion, just point in the right direction. I don't know if at the time we emit the error for `pick_first` we're able to evaluate the function's body to see if changing the lifetimes is feasible or not.
Thinking as a beginner, I think part of the problem here is the compiler is overstating its case. With experience, one learns to read this message as "borrowed value could not be proved to live as long as required by the function declaration", but that's not what it says! It asserts that the value in fact does not live long enough, which is clearly not true.
(Edit: having said this, I now realize the short version confuses beginners because of the definition of “enough”. They read it as “does not live long enough to be safe”, which the compiler is not—and cannot be—definitively saying.)
When this happens in a more complex situation (say, involving a deeper call tree and struct member lifetimes as well), you just get this same basic message, and finding the place where you've unnecessarily tied two lifetimes together can be a bit of a hunt.
My impression is that it's difficult or impossible for the compiler to "explain its reasoning" in a more complex case (I made an example at [0] [1]), which is understandable, but it does mean you always get this bare assertion "does not live long enough" and have to work through the tree of definitions yourself to find the bad constraint.
[0] https://play.rust-lang.org/?version=stable&mode=debug&editio...
[1] https://play.rust-lang.org/?version=stable&mode=debug&editio...