Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 660
ayush prashar Avatar asked Mar 14 '19 09:03

ayush prashar


People also ask

How does lifetime work in Rust?

Lifetimes are what the Rust compiler uses to keep track of how long references are valid for. Checking references is one of the borrow checker's main responsibilities. Lifetimes help the borrow checker ensure that you never have invalid references.

What are lifetime parameters Rust?

Rust uses lifetime parameters to avoid such potential run-time errors. Since the compiler doesn't know in advance whether the if or the else block will execute, the code above won't compile and an error message will be printed that says, “a lifetime parameter is expected in compare 's signature.”

What does static lifetime mean in Rust?

Reference lifetime. As a reference lifetime 'static indicates that the data pointed to by the reference lives for the entire lifetime of the running program. It can still be coerced to a shorter lifetime.

WHAT IS A in Rust?

The 'a reads 'the lifetime a'. Technically, every reference has some lifetime associated with it, but the compiler lets you elide (i.e. omit, see "Lifetime Elision") them in common cases. fn bar<'a>(...)


2 Answers

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:

  • How can this instance seemingly outlive its own parameter lifetime?
like image 131
E_net4 stands with Ukraine Avatar answered Oct 12 '22 22:10

E_net4 stands with Ukraine


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
like image 5
Peter Hall Avatar answered Oct 12 '22 21:10

Peter Hall