There's multiple ways to represent “functions” in Rust. When in need for any single type that can represent multiple different functions, we can narrow the choice down to function pointers and Fn
/FnMut
/FnOnce
trait objects.
Your code example seems like you are asking for a way to have the returned some_fun
be callable in and by itself, which means that some_fun
must be some sort of value that also holds some sort of pointer to the original self
(as self
is available within the func_for_a
/func_for_b
functions, yet not passed in manually to the some_fun()
) call. If a function wants to keep around any additional information, such as a pointer, it needs to be a closure, and the type to use is a trait object for one of the Fn
traits. In this case let’s go with FnMut
, because the self
pointer is a mutable reference:
struct Parser {
change_some_variable_to_five: i32,
}
enum SomeEnum {
A, B
}
impl Parser {
fn func_for_a(&mut self, param: String) {
println!("In A: {}", param);
}
fn func_for_b(&mut self, param: String) {
println!("In B: {}", param);
}
fn choose(&mut self, sort: SomeEnum) -> Box<dyn FnMut(String) + '_> {
match sort {
SomeEnum::A => Box::new(|p| self.func_for_a(p)),
SomeEnum::B => Box::new(|p| self.func_for_b(p)),
}
}
fn main(&mut self, sort: SomeEnum) {
let mut some_fun = self.choose(sort);
// Have function but can do things before calling it!
// self.change_some_variable_to_five = 5;
// Call function afterwards
some_fun(String::from("hello"));
}
}
Now, with this example code, once you uncomment the self.change_some_variable_to_five = 5
line, you’ll run into the consequences of keeping the mutable reference (self
) around for a long-ish time as part of the some_fun
closure.
Compiling playground v0.0.1 (/playground)
error[E0506]: cannot assign to `self.change_some_variable_to_five` because it is borrowed
--> src/lib.rs:25:9
|
23 | let mut some_fun = self.choose(sort);
| ----------------- `self.change_some_variable_to_five` is borrowed here
24 | // Have function but can do things before calling it!
25 | self.change_some_variable_to_five = 5;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self.change_some_variable_to_five` is assigned to here but it was already borrowed
26 | // Call function afterwards
27 | some_fun(String::from("hello"));
| -------- borrow later used here
For more information about this error, try `rustc --explain E0506`.
error: could not compile `playground` due to previous error
Approaches for avoiding such conflict and making the borrow checker include the (sometimes dreaded) shared mutability constructs on one hand, or – in possible – one likes to avoid the problem by keeping a more keen eye on your references and only handing them to the functions that need them, when they need them, and for a short amount of time. This amounts in making the self
argument an argument of the function that choose
returns, e.g. Box<dyn FnMut(&mut Self, String) + '_>
or Box<dyn Fn(&mut Self, String) + '_>
, but by this point, the function you return does not actually require any run-time information anymore, so a functiton pointer would work as well: Function pointer types in Rust are written like e.g. fn(&mut Parser, String)
, and can be created from functions via implicit conversion; the way to name the function func_for_a
itself is via a path like Parser::func_for_a
(or Self::func_for_a
since Self
is an alias to Parser
).
struct Parser {
change_some_variable_to_five: i32,
}
enum SomeEnum {
A, B
}
impl Parser {
fn func_for_a(&mut self, param: String) {
println!("In A: {}", param);
}
fn func_for_b(&mut self, param: String) {
println!("In B: {}", param);
}
fn choose(&mut self, sort: SomeEnum) -> fn(&mut Parser, String) {
match sort {
SomeEnum::A => Self::func_for_a,
SomeEnum::B => Self::func_for_b,
}
}
fn main(&mut self, sort: SomeEnum) {
let mut some_fun = self.choose(sort);
// Have function but can do things before calling it!
self.change_some_variable_to_five = 5;
// Call function afterwards
some_fun(self, String::from("hello"));
}
}
Often, it’s also possible to avoid the need for function pointers altogether. E.g. in this case, the fn(&mut Parser, String)
might actually be not much more useful than the SomeEnum
enum you started out with. (Well, it may depend on your use-case, and how extendable you want the type to be.) So perhaps your best approach might be to just create a dispatching method
fn func_for_either(&mut self, sort: SomeEnum, param: String) {
match sort {
SomeEnum::A => self.func_for_a(param),
SomeEnum::B => self.func_for_b(param),
}
}
and then call that directly
fn main(&mut self, sort: SomeEnum) {
let my_sort = sort;
// Have function but can do things before calling it!
self.change_some_variable_to_five = 5;
// Call function afterwards
self.func_for_either(my_sort, String::from("hello"));
}