Search code examples
rustlifetimeborrow-checkertype-safety

Can rust reason about lifetimes on all control flow paths?


Consider the following example:

impl Foo {
    fn measure<'a>(&'a self) -> Vec<&'a Bar> {...}

    fn try_action<'a>(&'a mut self) -> Result<(), Vec<&'a Bar>> {
        let measurement = self.measure(); // this creates references that we may wish to return from this scope
        if measurement.is_empty() {
            drop(measurement); // just to be explicit and prove this is sound
            self.baz = 0;
            Ok(())
        } else {
            Err(measurement) // here we don't mutate self, instead we want to return a reason why we couldn't
        }
    }
}

As of rust 1.57.0, the borrow checker will not accept this program. It is upset that I cannot borrow *self as mutable because of the existence of an immutable borrow (the one created by the call to measure).

My understanding of this is that it believes there is an issue because the references contained within measurement technically can last until the end of the function scope (the else block case). However, as humans we can see this should be sound because all immutable borrows are dropped before any mutation takes place. We don't drop them on all control flow paths, but the ones where they live on do not have any mutations on them. As such, for all control flow paths we either drop the immutable borrows (although a lack of use should suffice here), OR we never mutate the structure.

Why can rust not reason about this? Is this a bug in the logic of rustc? Is this a limitation of the design of lifetimes? Is this unsound?


Solution

  • Your understanding is not wrong. This is a limitation of the current borrow checker implementation, specifically documented as problem #3 of the non-lexical-lifetimes RFC. Essentially, since measurement is a reference that is returned, the lifetime must exist until the end of the function, which will encompass the entire if statement without regard to the drop.

    There are improvements in the works (the next generation borrow checker polonius), but it may still be a while before that lands on stable.