dtolnay/no-panic

Lifetime error on methods that accept &mut self and return a borrow of self

saethlin opened this issue · 3 comments

#[macro_use]
extern crate no_panic;

struct Foo {
    data: usize,
}

impl Foo {
    #[no_panic]
    fn get_mut(&mut self) -> &mut usize {
        &mut self.data
    }
}

fn main() {}

with NLLs I see this (the current borrow checker message is very verbose):

error: captured variable cannot escape `FnMut` closure body
  --> src/main.rs:12:9
   |
10 |     #[no_panic]
   |               - inferred to be a `FnMut` closure
11 |     fn get_mut(&mut self) -> &mut usize {
12 |         &mut self.data
   |         ^^^^^^^^^^^^^^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

And if change the return type to be a &usize, a slight variation on it

  --> src/main.rs:12:9
   |
10 |     #[no_panic]
   |     -----------
   |     |         |
   |     |         return type of closure is &'2 usize
   |     lifetime `'1` represents this closure's body
11 |     fn get_mut(&mut self) -> &usize {
12 |         &self.data
   |         ^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure

To be honest, I'm at a loss to explain this. Everything is fine if the method accepts &self. Is this a limitation of the existing borrow checkers?

Seems like a borrow checker limitation! For now you can work around it by writing:

#[no_panic]
fn get_mut(&mut self) -> &mut usize {
    &mut {self}.data
}

I would love a PR to fix this.

Can you explain to me how that fix works? I think this is way beyond me.

For move closures there appears to be a difference between reborrowing from the input reference, in which case the reborrow lives only as long as the closure, vs moving ownership of the original borrow into the closure, which lives as long as the input reference. Curly braces result in the value being moved rather than reborrowed.

struct S {
    data: usize,
}

fn does_not_compile(s: &mut S) -> &mut usize {
    (move || &mut s.data)()
}

fn ok(s: &mut S) -> &mut usize {
    (move || &mut {s}.data)()
}

#[no_panic] internally uses a move closure to insert a check just before the function returns for whether it is returning normally or unwinding from a panic.