If my question is prompted by the “Advanced Features” section of the book, it must be “advanced”, right…?
I took the example that ends on page 428 of The Book and added a little extra, to see run it (see what gdb thinks, now that I figured out how to set a breakpoint in my code), make the reality real, so it will all be clear. It isn’t clear.
I have this.
In main.rs:
#![feature(extern_prelude)] // Compiler told me to put this here.
// Donno what it does.
fn main() {
let y = lifetimes::try_it();
println!("y: {:?}", y);
}
In lib.rs:
struct Context<'s>(&'s str);
struct Parser<'c, 's: 'c> { // s lives at least as long as c
context: &'c Context<'s>,
}
impl<'c, 's> Parser<'c, 's> {
fn parse(&self) -> Result<(), &'s str> {
Err(&self.context.0[1..])
}
}
fn parse_context(context: Context) -> Result<(), &str> {
Parser { context: &context }.parse()
}
pub fn try_it<'a>() -> Result<(), &'a str> {
let x = Context("def");
let y = parse_context(x);
y
}
When I run it I see:
y: Err("ef")
Cool. But why did it work? Where is that “ef” sitting? When I called try_it(), didn’t x get allocated on the stack inside try_it()? By the time I print it in main(), isn’t it long gone? Where is my “ef”?
Thanks,
-kb, the Kent who is convinced that lifetimes will be clear as a bell, any second now, for sure Rocky.
“def” being a “str” literal is an &str (string-slice) with a 'static lifetime (all str literals are compiled statically into the final binary). Does that give you a hint?
How long does anyone care about my choice of letter? For the span of that source file? Something smaller? Because when I say:
impl<'c, 's> Parser<'c, 's> {
That first <> feels like it holds a declaration and the second <> feels like it holds a use. But in this case they both seem to have refer to lifetimes designators that have been mentioned a couple lines above.
-kb, the Kent who feels like he understands the problem of lifetimes but is confused by these dang tick marks that are supposed to fix things.
No no, they don't refer anything. Basically, you can think of lifetime designators as regular generic type arguments, with the exception that they are used for checking the borrowing rules by the compiler, and they are stripped away from the resulting code.
If you have a struct
struct Hello<'a> {
name: &'a str
}
you can follow with
impl<'whatever> Hello<'whatever> {
...
}
The latter is required because the struct requires it (holds a use), the former is a declaration of a lifetime (holds a declaration).
So the 'a only exists for the struct, and 'whatever is only defined for the impl. Does this help you?
struct Hello<'a> {
// ^^
// the first appearance, defines a lifetime, may be arbitrary named
name: &'a str
// ^^
// then, you use the designator defined above
}
Now you want to add some functions for this struct. You can't say
impl Hello { ... }
because Hello requires a lifetime. Let's rewrite:
impl Hello<'b> { ... }
// ^^
// may be arbitrary named. I'm using a different name to show you
// that it doesn't have to be 'a from the struct declaration above
Now the compiler complains that the 'b thing is not defined. Let's define it:
Consider an example with type arguments instead of lifetimes:
enum Option<T> {
Some(T),
None
}
impl<Whatever> Option<Whatever> {
pub fn to_inner(self) -> Whatever {
match self {
Option::Some(inner) => inner,
Option::None => panic!("to_inner called on Option::None")
}
}
}
It works the same way: Whatever doesn’t refer to T. Instead, they both refer to some (but the same) type:
fn main() {
// opt is Option<u32>
let opt = Option::Some(123u32);
// to_inner is Option<u32> -> u32
// even through `Option` was declared as Option<T>,
// and `to_inner` as to_inner(self) -> Whatever
assert_eq!(opt.to_inner(), 123);
}