When it is not possible to infer the lifetime in the Rust borrow checker?

Viewed 294

In most cases, the Rust compiler can infer the lifetime. if the lifetime scope is determined at runtime, it says that the lifetime must be explicitly marked.

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

Here,

  • The lifetime is generic.
  • This means that there is a scope bound to the lifetime 'a after the function result is returned.
  • the compiler can know information that the memory is valid for the minimum lifetime 'a.

I'am very curious. instead of using a lifetime syntax, can't the compiler just adopt the fewer scope area to which lifetime 'a can be bound?

fn main() { //larger scope
    let s1 = String::from("long string is long");

    { //fewer scope
        let s2 = String::from("xyz");
        let result = longest(s1.as_str(), s2.as_str());
        println!("The longest string is {}", result); 
    } 
      
}

Even if the call stack is more complex on the caller side, the scope area is determined at the time of borrowing, so the same question seems to be possible.

fn func1<'a>(x: &'a str, y: &'a str) {
   let c = String::from("hello");
   let result = func2 (a,b,c)
   ...
}
1 Answers

I think you're approaching the question the wrong way. When compiling a function such as the main() you've shown, the borrow checker doesn't examine the contents of individual functions it calls, like longest(), it only examines their signatures. This is a feature: it allows the implementation of a function to change without affecting the guarantees provided by its signature. If main() successfully compiles, you can be sure that it will keep compiling however you modify longest as long as you don't change its declaration.

In case of longest(), the unannotated signature is ambiguous:

fn longest(x: &str, y: &str) -> &str

// does the above mean:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str // returns sub-slice of x
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'b str // returns sub-slice of y
fn longest<'c>    (x: &'c str, y: &'c str) -> &'c str // returns sub-slice outlived by x and y
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'static str  // returns static data

Without lifetime annotations we can't tell from just looking at the declaration whether the returned &str comes from the first &str, the second &str, a common lifetime, or possibly a static &str. For very simple functions, like those that accept and return a single reference, the compiler performs "lifetime elision" where it mechanically picks the "obvious" interpretation of the unannotated signature, allowing you to write fn longest(x: &str) -> &str as a simple shorthand for fn longest<'a>(x: &'a str) -> &'a str. But when a function accepts multiple references, the compiler refuses to guess and makes you spell out what you wanted.

As pointed out at the beginning of the answer, and this often confuses beginners, what the compiler definitely refuses to do is infer the lifetime signatures from the function body, because that would make the signature dependent on the implementation.

Related