dtolnay / no-panic

Attribute macro to require that the compiler prove a function can't ever panic
Apache License 2.0
1k stars 13 forks source link

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

Closed saethlin closed 6 years ago

saethlin commented 6 years ago
#[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?

dtolnay commented 6 years ago

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.

saethlin commented 6 years ago

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

dtolnay commented 6 years ago

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.