Why can't the Rust compiler infer that one argument outlives another?
Asked Answered
F

2

6

I have the following code:

struct Solver<'a> {
    guesses: Vec<&'a str>,
}

impl<'a> Solver<'a> {
    fn register_guess(&mut self, guess: &'a str) {
        self.guesses.push(guess);
    }
}

fn foo(mut solver: Solver, guess: &str) {
    solver.register_guess(guess)
}

It doesn't compile:

   |
11 | fn foo(mut solver: Solver, guess: &str) {
   |        ----------                 - let's call the lifetime of this reference `'1`
   |        |
   |        has type `Solver<'2>`
12 |     solver.register_guess(guess)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`

The error message says that the argument guess must outlive solver. It's plainly obvious to me that that's true: the lifetime of solver ends at the end of the function, and the lifetime of guess doesn't. This seems like something the compiler should be able to infer, and compile without error.

Why isn't that the case? Does this code actually have some way for solver to outlive guess? Or is it just that the compiler doesn't try to do this kind of inference at all?

I know how to fix it --- change the function to fn foo<'a>(mut solver: Solver<'a>, guess: &'a str) --- but I'm asking why I should have to do that.

Feininger answered 9/10, 2022 at 22:53 Comment(0)
H
6

While solver itself can't outlive guess, the lifetime it refers to very well could. For example, imagine invoking foo() with a Solver<'static>. That kind of solver would expect guess to be &'static str and might store the data referred to by guess in a global variable. (Remember that the compiler doesn't consider what register_guess() does while borrow-checking foo(), it just considers its signature.)

More generally, Solver<'a> might contain references to 'a data that outlives solver itself. Nothing stops register_guess() from storing the contents of guess inside such references. If guess isn't guaranteed to live at least as long as 'a, then foo() is simply unsound. For example, take this alternative definition of Solver:

struct Solver<'a> {
    guesses: &'a mut Vec<&'a str>,
}

With unchanged signature of register_guess(), foo() would allow unsound code like this:

fn main() {
    let mut guesses = vec![];
    let solver = Solver { guesses: &mut guesses };
    {
        let guess = "foo".to_string();
        // stores temporary "foo" to guesses, which outlives it
        foo(solver, guess.as_str());
    }
    println!("{}", guesses[0]); // UB: use after free
}
Hillie answered 13/10, 2022 at 14:46 Comment(0)
B
-1

This error comes from rust's rules of lifetime elision. One of this rules states that:

Each elided lifetime in the parameters becomes a distinct lifetime parameter

Rust conservatively assumes that each not specified lifetime is different. If you want some lifetimes to be equal you must specify it explicitly. Your problem is equivalent to simple function that takes two string slices and returns the longer one. You must write the signature of such function as fn longer<'a>(&'a str, &'a str) -> &'a str, or the compiler will give you the same error.

Butyrin answered 9/10, 2022 at 23:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.