Good news: your code compiles I'm going to show my analysis of the code, with the caveat that I don't know if my logic matches the compiler's logic exactly, but I think I'll be close enough to help in understanding.
Your Something
struct doesn't hold any references, so there aren't any generic lifetime parameters needed on the struct definition.
The c
method has a parameter that's an immutable reference to self
and a return type that's a string slice. Because of lifetime elision, adding lifetime parameters to these references isn't required, but we're allowed to annotate the lifetimes if we want to (just as we could annotate types with let
definitions if we want to). The elided lifetime parameters correspond to this code:
impl Something {
fn c<'a>(&'a self) -> &'a str {
self.a.as_str()
}
}
which says the returned string slice lives as long as self
does.
Now, on to main
-- I'm going to try to annotate the concrete lifetimes with some colored lines that I've been playing with for the as-yet-unpublished Unit 4 of the Rust in Motion video course I'm working on (shameless self promotion!)
The concrete lifetime of the Something
instance owned by d
starts where d
is declared and lasts until d
goes out of scope at the end of main
, illustrated here by the solid blue line along the left:
The first time we call c
, it returns a string slice reference that is valid from where we bind that reference to the variable e
until the last usage of e
, in the final println!
. I've annotated that reference's lifetime with a blue dotted line, dotted because it's a borrowed value rather than an owned one:
Similarly, the second time we call c
, it returns a string slice reference that is valid from where we bind that reference to the variable f
until the last usage of f
, also in the final println!
. Illustrated here again with a dotted blue line:
The compiler looks at the lifetimes of the owned value and the references to it, sees that the references don't outlive the value they're referencing, and therefore declares that this code is valid because all of the references will always be valid.
If we change this function a bit we can make it more interesting and see the compiler preventing a problem.
fn main() {
let e = {
let d = Something {
a: String::from("Hello"),
b: String::from("Bye"),
};
d.c()
};
println!("{}", e);
}
Here, the concrete lifetime of the Something
instance owned by d
starts where d
is declared and ends at the end of the inner scope where d
is cleaned up, illustrated here by a solid blue line:
This code is trying to bind the reference returned by the call to c
to the variable e
, so the lifetime of the reference would need to last from where e
is created until the last time e
is used, in the final println!
, illustrated here by a blue dotted line:
The compiler looks at this code and sees that the reference outlives the value it is pointing to, and the compiler rejects this code as invalid with this error message:
error[E0597]: `d` does not live long enough
--> src/main.rs:21:9
|
14 | let e = {
| - borrow later stored here
...
21 | d.c()
| ^ borrowed value does not live long enough
22 |
23 | };
| - `d` dropped here while still borrowed
What this is saying is that because d
is cleaned up at the end of the inner scope on line 23, the reference in e
would be invalid (known as a "dangling reference") and wouldn't point to what we want it to point to when we use e
in the println!
.
Whew! Did that help at all?