I am trying to figure out how I can call a closure which is a member of structure from one of the structures methods.
An simplified example is is below, ideally I want to be able to pass a closure to. struct which manipulates some of the struct's data members when a generate() function is called. This will allow the calling code to change the behaviour of the internal algorithim:
error[E0596]: cannot borrow `self` as mutable, as it is not declared as mutable
--> src/main.rs:10:26
|
10 | (self.operation)(&mut self);
| ^^^^^^^^^ cannot borrow as mutable
|
note: the binding is already a mutable borrow
--> src/main.rs:9:17
|
9 | fn do_stuff(&mut self) {
| ^^^^^^^^^
help: try removing `&mut` here
|
10 - (self.operation)(&mut self);
10 + (self.operation)(self);
|
error[E0502]: cannot borrow `self` as mutable because it is also borrowed as immutable
--> src/main.rs:10:26
|
10 | (self.operation)(&mut self);
| ---------------- ^^^^^^^^^ mutable borrow occurs here
| |
| immutable borrow occurs here
| immutable borrow later used by call
Some errors have detailed explanations: E0502, E0596.
For more information about an error, try `rustc --explain E0502`.
error: could not compile `recursive_closure` due to 2 previous errors
The problem line is (self.operation)(&mut self);.
I am new to rust and this is a pattern I have previously used with C++ in the past.
fn do_stuff(&mut self) is shorthand for fn do_stuff(self: &mut Self). Because self already has the type &mut Self, the statement (self.operation)(&mut self) is re-borrowing the mutable reference. However, the variable self isn't mut self. This is what the compiler error is indicating -- self can't be borrowed mutably because it isn't declared as such. The compiler suggestions are sadly not very good in this situation.
The easier solution is to use (self.operation)(self) because self is already the correct type needed for the function call.
I believe the compiler will begin complaining that self can't be borrowed mutably multiple times -- that isn't as easy of a fix, and generally means restructuring your types to avoid the need for multiple mutable borrows of the same data.
Ok son changing the call in do_stuff(&mut self) to (self.operation)(self); results in the following:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:10:9
|
10 | (self.operation)(self);
| ----------------^^^^^^
| |
| mutable borrow occurs here
| immutable borrow occurs here
| immutable borrow later used by call
For more information about this error, try `rustc --explain E0502`.
error: could not compile `recursive_closure` due to previous error
In my original use case I restructured the code but I am not happy with the solution as I would like to store a closure that operates on some_type inside the type so that its internal algorithm(s) can use the closure.
The main issue is that the function call borrows self.operation immutably, but it gives the closure mutable access to self.operation through the &mut Foo parameter. While the most idiomatic solution is to extract message and val into their own type and pass that to operation, I see two other solutions with the current Foo type. The first is to take ownership of operation by replacing it with a no-op placeholder:
But note that do_stuff() cannot be called recursively this way, and if operation() panics, then self will be left with the placeholder. (The latter issue can be fixed with a drop guard.) Perhaps a more robust solution is to place operation in an Rc, to allow shared ownership:
This has none of the caveats of the former solution. Note that since operation is behind an Rc, it can't always be moved out of the Foo, but this is unlikely to be a major issue in practice.
Extracting the variables into their own type is the solution I ended up with in the full use case but seeing the closure with the data is my preferred solution. Your explanation certainly clears up my understanding of what is happening and the second solution provided is nice a neat (effectively changing the storage of operation to an Rc and cloning operation prior to the call).