Search code examples
rustreferencelifetime

How does Rust detect a reference to a local variable without the function being used?


Assume a function returns a dangling reference to a local variable:

fn foo<'a>() -> &'a i32 {
    let i = 2;
    &i
}

fn main() { }

Rust notices this and raises an error.

Adding lifetimes:

fn foo<'a>() -> &'a i32 {
    'b: {
        let i = 2;          // i has lifetime 'b
        &'c i
    }
}

Rust infers 'c = 'b because &i refers to i. Moreover, 'a is instantiated as 'c and hence 'b because the return type is &'a i32 and the returned value is &'c i32. I do not see why an error should occur here, since all lifetimes can be instantiated with concrete values without any mismatches.

If main was

fn main() {
    'd: {
        let i_ref: &'d i32 = foo();    // foo() has lifetime 'b
    }
}

then we still would have 'a = 'c = 'b, but in main there is a lifetime mismatch since 'b does not contain 'd. In that case, I would expect an error.

How does Rust detect the dangling reference in the first case without any invocation of foo?


Solution

  • All lifetimes passed into a non-async function have to be valid for the entire function body - the lifetime can't spontaneously come into existence or become invalid during the function's execution.

    This rule automatically precludes returning local references, since their lifetime effectively ends right before the function returns.

    (Async functions are a bit different - under the hood, they return an opaque future object that may or may not hold the reference. But they still prevent returning references to stack allocated variables.)


    A different way of thinking about it:

    foo<'a>() -> &'a i32 let the caller choose whatever lifetime it wants, as long as the lifetime of the (nonexistent) arguments match the return value. I could conceivably call foo::<'static>(), which, by the rules of the function signature, must be accepted, but obviously won't be valid for a function returning a local variable.