简体   繁体   中英

Do lifetime annotations in Rust change the lifetime of the variables?

The Rust chapter states that the annotations don't tamper with the lifetime of a variable but how true is that? According to the book, the function longest takes two references of the strings and return the longer one. But here in the error case

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

it does actually change the lifetime of the result variable, doesn't it?

We've told Rust that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in.

What the book is merely suggesting is that a lifetime parameter of a function cannot interfere with the affected value's lifetime. They cannot make a value live longer (or the opposite) than what is already prescribed by the program.

However, different function signatures can decide the lifetime of those references . Since references are covariant with respect to their lifetimes, you can turn a reference of a "wider" lifetime into a smaller one within that lifetime.

For example, given the definition

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str

, the lifetimes of the two input references must match. However, we can write this:

let local = "I am local string.".to_string();

longest(&local, "I am &'static str!");

The string literal, which has a 'static lifetime, is compatible with the lifetime 'a , in this case mainly constrained by the string local .

Likewise, in the example above, the lifetime 'a has to be constrained to the nested string string2 , otherwise it could not be passed by reference to the function. This also means that the output reference is restrained by this lifetime, which is why the code fails to compile when attempting to use the output of longest outside the scope of string2 :

error[E0597]: `string2` does not live long enough
  --> src/main.rs:14:44
   |
14 |         result = longest(string1.as_str(), string2.as_str());
   |                                            ^^^^^^^ borrowed value does not live long enough
15 |     }
   |     - `string2` dropped here while still borrowed
16 |     println!("The longest string is {}", result);
   |                                          ------ borrow later used here

See also this question for an extended explanation of lifetimes and their covariance/contravariance characteristics:

First, it's important to understand the difference between a lifetime and a scope. References have lifetimes, which are dependent on the scopes of the variables they refer to.

A variable scope is lexical:

fn main() {
    let string1 = String::from("long string is long"); // <-- scope of string1 begins here
    let result;
    {
        let string2 = String::from("xyz"); // <-- scope of string2 begins here
        result = longest(string1.as_str(), string2.as_str());
        // <-- scope of string2 ends here
    }
    println!("The longest string is {}", result);
    // <-- scope of string1 ends here
}

When you create a new reference to a variable, the lifetime of the reference is tied solely to the scope of the variable. Other references have different lifetime information attached to them, depending on where the reference came from and what information is known in that context. When you put named lifetime annotations on a type, the type-checker simply ensures that the lifetime information attached to any references is compatible with the annotations.

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        // The lifetime of result cannot be longer than `'a` 
        result = longest(string1.as_str(), string2.as_str());
        // But a reference to string2 also has lifetime `'a`, which means that
        // the lifetime `'a` is only valid for the scope of string2 
        // <-- i.e. to here
    }
    // But then we try to use it here — oops!
    println!("The longest string is {}", result);
}

We've told Rust that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in.

Sort of. We did tell this information to Rust, however, the borrow-checker will still check if it is true! If it's isn't already true then we will get an error. We can't change the truthfulness of that information, we can only tell Rust the constraints we want, and it will tell us if we are right.

In your example, you could make the main function valid by changing the lifetime annotations on longest :

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y // oops!
    }
}

But now you get an error inside longest because it no longer meets the requirements: it is now never valid to return y because its lifetime could be shorter than 'a . In fact, the only ways to implement this function correctly are:

  1. Return x
  2. Return a slice of x
  3. Return a &'static str — since 'static outlives all other lifetimes
  4. Use unsafe code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM