Nested mutation in methods

I just found that this little snippet is not accepted in rust. I have written similar code in swiftlang without any issues. How can I make it work?

struct A { a: () }
 
impl A {
   fn b(&self) -> () { self.a }
   fn a(&mut self, mut pred: impl FnMut() -> bool) {
      let _ = pred();
   }
   fn __(&mut self) {
      self.a(||{ let _ = self.b(); return false; })
   }
}

On this level of abstraction, you can’t “make it work”. Or you can trivially make it work. Depending on the question of what can or cannot change. Why is the method &mut self? (You change it to &self if that’s reasonable.) Is accessing self in the closure common? (You could make it impl FnMut(&mut Self) -> bool [or perhaps with FnMut(&Self) -> …] and pass self when calling it if that’s reasonable.) Etc…

Only the more concrete context / code / problem-you’re-solving can answer these questions and determine the best approach.

So, if you need further help, feel free to answer / follow-up with more context on why you have this code pattern. Also, in case you’re having trouble understanding why the code isn’t accepted by the compiler as-is in the first place, feel free to ask for an explanation.

2 Likes

This looks like a solution, but I don't see what essentially changes when I pass Self explicitly vs when I capture it. Can you elaborate on that?


What do you mean by this? What other levels are there?

My bad, this should have been written like this:

fn a(&mut self, mut pred: impl FnMut() -> bool) {
      let _ = pred();
      self.a = ();
   }

Oh, well, I’m saying that in case you were to say about sufficiently many API-design details that they must not change, then there might be no more way left of “making it work”. I.e., as I’ve clarified later, there’s no clear / canonical way to make it work, and how easy the problem is to solve depends entirely on more concrete considerations.


The thing… well, one of the things… that Rust’s borrow checker prevents is shared and mutable access to a value. So you can’t have two (usable/active) &mut … references to the same value at the same time. (Or even one &mut … and one &… reference.)

Now, closures work by capturing local variables. E.g. the ||{ let _ = self.b(); return false; } closure mentions self in a fn (&self) method call (for b()), so it needs access to a &Self reference; this reference is captured, i.e. it becomes part of the closure itself. Passing this closure into the self.a(…) call, alongside the receiver self: &mut Self means that there are two references to the same value passed as different arguments to a method call, so both values are active / in-use at the same time, yet at least one of them has mutable access.

Now, with impl FnMut(&mut Self) -> bool, you can have the closure not capture self, but receive it as an argument. The full change is

struct A { a: () }
 
impl A {
   fn b(&self) -> () { self.a }
   fn a(&mut self, mut pred: impl FnMut(&mut Self) -> bool) {
      let _ = pred(self);
   }
   fn __(&mut self) {
      self.a(|this|{ let _ = this.b(); return false; })
   }
}

Now, there’s no more second reference to the same destination as self in the closure passed to the self.a(…) call as part of the closure. Inside of the implementation of fn a(…), you can then re-borrow self to pass a short-lived reference to the closure to use while the call lasts. In many cases, this is not a terribly restrictive thing to do, but it does mean that you couldn’t to something like e.g.

let mut some_iterator = self.some_field.iter();
// do some things with `some_iterator`
/* call */ pred();
// do some more things with `some_iterator`

anymore, because that would now look like

let mut some_iterator = self.some_field.iter();
// do some things with `some_iterator`
/* call */ pred(self);
// do some more things with `some_iterator`

and result in a compiler error pointing out something along the lines of

error[E0502]:  cannot borrow `self` as mutable because it is also borrowed as immutable

let mut some_iterator = self.some_field.iter();
                        ---- immutable borrow occurs here
// do some things with `some_iterator`
/* call */ pred(self);
                ^^^^ mutable borrow occurs here
// do some more things with `some_iterator`
                             ------------- immutable borrow later used here
5 Likes