So, in:
fn v1<'a> (a:~[&'a str]) -> ~[&'a str] {
return a;
}
#[test]
fn test_can_create_struct() {
let x = v1("Hello World".split(' ').collect());
}
I know, I've read http://static.rust-lang.org/doc/master/guide-lifetimes.html#named-lifetimes, but I don't understand what this code actually does.
The function is basically parametrized like a generic fn but with a lifetime, is what I've seen said on the IRC channel, but lets imagine that is the case, and we have a L, which is some specific lifetime structure.
Apparently I'm implicitly calling:
v1::<L>("Hello World".split(' ').collect());
..but I'm not. The lifetime being passed to this function is an instance of a lifetime, its not a TYPE of lifetime, so that comment doesn't make any sense to me.
I mean, I basically understand whats going on (I think): The returned ~[&str]
has the same lifetime as the scope of the caller, presumably the test_can_create_struct()
function. That's because (as I understand it) the function v1
is invoked with the lifetime instance from the calling function.
Very confusing.
Then we have some other examples like: https://gist.github.com/bvssvni/8970459
Here's a fragment:
impl<'a> Data<'a> {
pub fn new() -> Data<'a> {
Data { a: None, b: None }
}
pub fn a(&'a mut self, a: int) -> State<'a, Step1> {
self.a = Some(a);
State { data: self }
}
}
Now here I naively assumed that the Data<'a>
means that the lifetime instance for the function a()
is the same.
i.e. If you create a Data
(let blah = Data::new()
) and call blah.a()
, the lifetime is inherited from the create call; i.e. the State
object returned will exist for as long as the parent Data
object does.
...but apparently that's wrong too. So I have just no idea what the lifetime variables mean at all now.
Help!
So the easiest way to answer this will be to take a step back and walk you through what a lifetime actually is.
Lets take a simple function:
fn simple_function() {
let a = MyFoo::new();
println("{}", a);
}
In this function, we have a variable, a
. This variable, like all variables, lives for a certain amount of time. In this case, it lives to the end of the function. When the function ends, a
dies. The lifetime of a
, can then be described as starting at the beginning of the function, end ending at the end of the function.
This next function won't compile:
fn broken_function() -> &MyFoo {
let a = MyFoo::new();
return &a;
}
When you do &a
, you are borrowing a reference to a
. The thing about borrowing, though, is that you are expected to give the thing you borrowed back. Rust is very strict about this, and won't let you have references you can't return. If the thing you borrowed your reference from isn't around any more, you can't return the reference and that's just not on.
What it means for our broken_function
is that, because a
dies at the end of the function, the reference can't escape the function, because that would make it outlast a
.
The next step is this:
fn call_fn() {
let a = MyFoo:new();
{
let a_ref = &a;
let b = lifetimed(a_ref);
println!("{}", *b);
}
}
fn lifetimed<'a>(foo: &'a MyFoo) -> &'a MyBar {
return foo.as_bar();
}
Here are two functions, call_fn
and lifetimed
, there's some subtle stuff going on here, so I'll break it down.
In call_fn
I first create a new instance of MyFoo
and assign it to a
, then, I borrow a reference to a
and assign it to a_ref
. The thing with borrowing is that when you do a borrow, the lifetime information is transfered from the variable you are borrowing, into the reference itself. So now a_ref
, as a variable, has its own lifetime, which starts and ends at the beginning and end of that inner scope, but the type of a_ref
also has a lifetime, the one transferred over from a
.
Concrete lifetimes can't be named, but lets pretend we can do it anyway by using numbers. If the lifetime of a
is #1
, then the type of a_ref
is &'#1 MyFoo
. When we pass a_ref
to lifetimed
, the compiler fills in the lifetime parameter 'a
like it does for other type parameters. lifetimed
's return type is a reference with the same lifetime, so the compiler fills in the space there. Effectively making a unique call to lifetimed(foo: &'#1 MyFoo) -> &'#1 MyBar
.
This is why lifetimes appear in the type parameter list, they are part of the type system, and if the types don't match up, that's an error. The compiler works out the lifetimes needed in order for the function to compile, so you never have to worry about it, but won't look outside the current function to get more information. You need to use the parameters to tell the compiler about the function you're calling so it knows that everything is ok.
NB: There is one lifetime you can explicitly name. 'static
which is the lifetime of things that last for the entire length of the program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With