A owns a B internally, and would like to call a function on it where B may mutate A. Obviously A must be mutable in the first place, so mutable_func_1() uses &mut self. However, the compiler complains when calling into B:
error[E0499]: cannot borrow `self.b` as mutable more than once at a time
--> src/main.rs:14:9
|
14 | self.b.co_function(self);
| ^^^^^^^-----------^----^
| | | |
| | | first mutable borrow occurs here
| | first borrow later used by call
| second mutable borrow occurs here
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:14:28
|
14 | self.b.co_function(self);
| ------ ----------- ^^^^ second mutable borrow occurs here
| | |
| | first borrow later used by call
| first mutable borrow occurs here
I'm struggling to understand how either b or self is borrowed more than once here. Is it possible to make this kind of call work?
&mut references are exclusive references, meaning that as long as they are active, nothing else, especially not another &mut reference, may access the memory they point to. If you were to successfully call co_function, then self and a.b would be two different ways to get to the same data, which fails to be exclusive and therefore must be prohibited.
In the language of the error message, the first borrow of self.b is the borrow of all of self (all of the A) by passing self (a reference) to co_function, and the second borrow is one you take by writing self.b.co_function as the receiver. If we were to “desugar” that method call into more explicit syntax, it would be:
B::co_function(&mut self.b, &mut *self);
That’s two borrows, both of which have access to self.b.
Solutions to this problem include:
Put all the fields of A that are not b into another struct (call it C) owned by A, and pass a reference to &mut C, rather than &mut A, to B::co_function(). Then have B call methods on C, not A.
Make B::co_function() not take &mut B at all, but only &mut A, so it has the whole access it needs through a single reference.
Temporarily take B out of A while calling its method. (This requires A to have a way to temporarily not own any B, e.g. Option<B>).
Change the methods on A which B wants to call to not be methods on A, but only take references they specifically need.
Which solution is best depends on the actual details of the relationship between A and B and what data each function needs. You cannot design the types of a Rust program abstractly without knowing the data flow (what data needs to be borrowed, moved, etc); you need to design the types so the right things can be borrowed for the right times, which depends on what data actually exists. Right now, I can't tell you what option to take because mutable_func_2() doesn’t actually manipulate any data from A.
This is a good question and very very common with Rust beginners.
For simple examples, the solutions are simple. The easiest thing here is to make co_function a method of A instead of B. Or, remove its self argument and call it with B::co_function(self).
In complicated cases you need more complicated solutions -- like those described in other answers here.
Looking at the call like that was very illuminating in understanding what's going on under the hood, thanks!
My specific use case is a plugin API with dynamically loaded extern "C" libraries, where during a call (eg. manager calls plugin.build_thing(&mut manager)), the plugin library can make calls back to the manager to advance the process (eg. manager.add_shape(), manager.set_colour()). Based on the above, I think what might be best is to extract the logic for building the thing into its own builder struct, where I can pass a mutable builder reference to the plugin (plugin.build_thing(&mut builder)). Once build_thing() returns, the manager can consume the builder.
Thanks for all the context, it's been very useful.
That sounds like a good plan. I would further say that it is probably a good idea to keep “owner of the plugins” separate from “context provided to the plugins” rather than making them the same type, regardless of whether or not doing so solves a borrowing problem.
Such separation makes for stronger typing (fewer operations that don’t make sense for a plugin to do) and can sometimes enable further functionality — for example, because channels have separate Sender and Receiver types, the receiver can know when the sender is dropped and will send no more messages, and vice versa, which is very powerful for automatic and correct cleanup.