The lifetime of borrowed data

Borrows can not last longer than the data value that they're borrowing. The Rust compiler has to make sure that no part of the program could allow that to happen, which means that it has to keep track of the lifetime of every borrow. In the examples we've seen so far, that's easy, because each borrow was created when we called a function and ended when the function returned, while the values that were borrowed lived until the end of the block expression that contained the function calls.

The lifetimes of the borrows were obviously shorter than the lifetimes of the variables, beginning later and ending sooner.

However, it's not hard to create situations where the compiler needs us to give it a hint about how long a borrow can exist, or how long the borrowed value will remain valid. We've already seen that once, when we used &'static str as the error type in Result. As we now know, this is an immutable reference to str , but there's still that 'static part to understand.

When we write something such as 'static or 'a after an & symbol, we're telling Rust that the lifetime of that reference has a name, which it recognizes because all lifetime names start with the ' symbol. If we say that a borrow's lifetime is named 'a, then we can use that name elsewhere to describe the relationship of that lifetime with the lifetimes of other borrows.

The static lifetime is special, because it's used for data values that are always available, as long as the program is running, such as the string constants we used as error messages in our examples earlier.

It's most useful to give names to lifetimes when we're defining functions, because we don't know what data values are going to be filled in to the function's parameter variables. If some of those parameters are borrows, we need to be able to tell Rust what our expectations are about the lifetimes of those borrows, so it can make sure that the code that calls our functions is doing it correctly.

Here is a function that Rust can't safely compile, because it needs to know more about the lifetimes than we've told it (yet):

pub fn smaller_x(value1: &Point2D, value2: &Point2D) -> &f64 {
if value1.x < value2.x {
&value1.x
}
else {
&value2.x
}
}

The problem here is that we're receiving two borrowed parameters in this function, each of which could have a different lifetime, and returning another borrowed value . Unfortunately, the Rust compiler doesn't know which parameter the return value will be borrowed from or what its lifetime is, and so it can't properly check the use of that value with code that calls our smaller_x function. Since it can't be sure everything is correct, the compiler simply refuses to try.

We can fix this by adding lifetime annotations:

pub fn smaller_x<'a>(value1: &'a Point2D, value2: &'a Point2D) -> &'a f64 {
if value1.x < value2.x {
&value1.x
}
else {
&value2.x
}
}

What we've done here is use the name 'a for the lifetimes of all three borrowed values, and also put 'a between < and >, between the function name and the parameter list. The < and > mark the beginning and end of the function's generic parameter list, which we'll talk about more in Chapter 7Generic Types. For now, what's important is that we're telling Rust that there is a lifetime that is equal to or shorter than the actual lifetimes of both value1 and value2, which is called 'a, and that the return value is safe to use within that 'a lifetime.

Specifying a lifetime name never changes the actual lifetime of a borrow. If value1 and value2 have different lifetimes, specifying 'a for them here doesn't make one of them last longer, nor does it shorten the span of the other one. When applied to the parameters, a lifetime name tells Rust that the named lifetime must be compatible with that parameter, meaning that the named lifetime must be wholly contained within the actual lifetime of the parameter. Then, when we use the same name for the return value's lifetime, we're telling Rust that the return value will only be guaranteed to be valid within the same limits—in this case, while both of the parameters are still valid.

Rust uses that guarantee to check the calling code. If we tried something like this, the Rust compiler would refuse, because we're trying to use the returned value in a way that might be incorrect, and Rust doesn't deal in maybes:

let main_4 = Point2D {x: 25.0, y: 25.0};
let smaller;
{
let main_5 = Point2D {x: 50.0, y: 50.0};
smaller = smaller_x(&main_4, &main_5);
}
println!("The smaller x is {}", smaller);

This is a block expression between the { and the }, like we saw in Chapter 2, Basics of the Rust Language, which means it has its own scope, which owns the main_5 variable. That means that, when we create a borrow of main_5, it has a shorter lifetime than a borrow of the main_4 variable. Rust looks at the function definition for smaller_x and sees that the return value is only guaranteed valid within the lifetimes of both main_4 and main_5, so trying to use it after the block expression has ended produces a compiler error.

This is a compiler error even though in fact main_4 contains the smaller_x, and so the return value is a borrow of a value that will still be valid when we get to the print command. Rust doesn't analyze the logic of a function when it's checking lifetimes, it just looks at what we've told it about the parameters and return.

This is a good thing. In this case, it would have been possible to examine the values used for the parameters, recognize that they are constant values that will always result in the same behavior from the function, and logically reason out that the lifetime of the returned borrow is equal to the lifetime of the first parameter. However, in general, that sort of reasoning would not be possible (what if the first parameter was input by the user?), and attempting it would just cause problems. Imagine changing the source of a variable's value, and suddenly having compiler errors way off in some other part of the program that shouldn't care! It's better to have these things as a concrete part of a function's interface.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.34.197