Problem with the borrow checker and mutable references using closures


#1

Sorry for the bad title, I can’t describe the problem more precisely.

I have a struct S, a struct Wrapper that stores a mutable reference to an S and a wrap method in S that may or may not create a Wrapper:

#[derive(Debug)]
struct S {
    id: i32,
}

impl S {
    fn wrap(&mut self, secret: &str) -> Result<Wrapper<'_>, ()> {
        if secret == "correct" {
            Ok(Wrapper {
                r: self,
            })
        } else {
            Err(())
        }
    }
}

#[derive(Debug)]
struct Wrapper<'a> {
    r: &'a mut S
}

I want to call this wrap method multiple times with different secrets until it works. If I do this manually, the borrow checker realizes that the borrow ends once I call unwrap_err and permits this code:

fn main() {
    let mut s = S { id: 1 };
    let a = s.wrap("wrong").unwrap_err();
    let b = s.wrap("correct").unwrap();
    println!("{:?}/{:?}", a, b);
}

But if I wrap this into a function foo that works for any function that creates a Result<Wrapper<'a>, ()>, the borrow checker complains about the second call to op and does not recognize that the borrow ended when I called unwrap_err for the first result:

fn foo<'a, F>(s: &'a mut S, op: F) -> ((), Wrapper<'a>)
where
    F: Fn(&'a mut S, &str) -> Result<Wrapper<'a>, ()>
{
    let err = op(s, "wrong").unwrap_err();
    let ok = op(s, "correct").unwrap();
    (err, ok)
}

fn main() {
    let mut s = S { id: 1 };
    let (a, b) = foo(&mut s, |s, secret| s.wrap(secret));
    println!("{:?}/{:?}", a, b);
}

Why exactly is this failing? What is the difference to the first example? And what can I do to make this work?

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0499]: cannot borrow `*s` as mutable more than once at a time
  --> src/main.rs:27:17
   |
22 | fn foo<'a, F>(s: &'a mut S, op: F) -> ((), Wrapper<'a>)
   |        -- lifetime `'a` defined here
...
26 |     let err = op(s, "wrong").unwrap_err();
   |               --------------
   |               |  |
   |               |  first mutable borrow occurs here
   |               argument requires that `*s` is borrowed for `'a`
27 |     let ok = op(s, "correct").unwrap();
   |                 ^ second mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.


API design: Enforcing mutually exclusive access
#2

playground

On mobile so a bit briefer than I’d like, but, foo receives a mutable borrow of S for caller-chosen lifetime 'a. The closure also receives the same lifetime borrow - this means that closure can hang on to that borrow for 'a, and that means you can’t call that closure twice.

The fix in the above playground is to simulate the same thing as your working example - the closure receives a short borrow (just the duration of the closure invocation), which allows you to call it twice.


#3

Thanks for your reply! I didn’t even know the for syntax in that context. :open_mouth:

Unfortunately I oversimplified the minimal example. In my actual code, the foo function takes another argument with a lifetime annotation, so the Wrapper needs an explicit lifetime annotation:

#[derive(Debug)]
struct Context<'a> {
    s: &'a str,
}

fn foo<F>(_ctx: Context<'_>, s: &mut S, op: F) -> ((), Wrapper<'_>)
where
    F: for <'a> Fn(&'a mut S, &str) -> Result<Wrapper<'a>, ()>,
{
    let err = op(s, "wrong").unwrap_err();
    let ok = op(s, "correct").unwrap();
    (err, ok)
}
error[E0106]: missing lifetime specifier
  --> src/main.rs:26:64
   |
26 | fn foo<F>(_ctx: Context<'_>, s: &mut S, op: F) -> ((), Wrapper<'_>)
   |                                                                ^^ expected lifetime parameter
   |
   = 

As soon as I start adding lifetime annotations, I get a “cannot infer an appropriate lifetime due to conflicting requirements” error.

fn foo<'w, 's, F>(s: &'s mut S, op: F) -> ((), Wrapper<'w>)
where
    F: for<'a> Fn(&'a mut S, &str) -> Result<Wrapper<'a>, ()>,
{
    let err = op(s, "wrong").unwrap_err();
    let ok = op(s, "correct").unwrap();
    (err, ok)
}

If I pull up 'a to function level, I can fix that problem by adding lifetime bounds. But then the borrow checker complains again.

fn foo<'a, 'w, 's, F>(s: &'s mut S, op: F) -> ((), Wrapper<'w>)
where
    's: 'a,
    'a: 'w,
    F: Fn(&'a mut S, &str) -> Result<Wrapper<'a>, ()>,
{
    let err = op(s, "wrong").unwrap_err();
    let ok = op(s, "correct").unwrap();
    (err, ok)
}

I feel like I would have to add the lifetime bound to the HRTB expression, but as far as I see, that’s not possible, right?

playground with context


#4

Same idea


#5

Nice, thank you! Is it possible to make that work with multiple wrapper types or do I have to duplicate the function? (example)


#6

Yeah, a trait in there makes things complicated. Lifetimes in traits are invariant, so whereas a compiler can see what’s happening with a concrete Wrapper<'_> as it makes its way out of the closure and then out of the foo function, there’s no way to express the same thing via generics (at least AFAIK).

Perhaps there’s some clever way to work around it, but I’d need to experiment/play around with it - nothing obvious springs to mind.