I am unsure whether I understand the reason for this borrow checker error.
The problem is moving the result of f(pop1.unwrap(), op2) into the push() method correct?
But why is this apparently caused because f is a reference?
/// applies a binary operator (+,-,*,/) to the top two
/// values on self.stack, op1 (+,-,*,/) op2
fn bin_op_on_stack<F>(& mut self, f:&F) -> ()
// vm::Value is a type alias for f64
where F: FnOnce(vm::Value, vm::Value) -> vm::Value
{
let pop1 = self.stack.pop();
// if pop2 is an f64 value pop1 must be one
// as well because of the FIFO operation
// principle of the stack.
let pop2 = self.stack.pop();
if let Some(op2) = pop2 {
// here is the error, f is underscored in red:
// cannot move out of `*f` which is behind a shared reference
// move occurs because `*f` has type `F`, which does not implement the `Copy` trait
self.stack.push(f(pop1.unwrap(), op2));
}
}
I also checked this variant:
if let Some(op2) = pop2 {
// same error message
let res = f(pop1.unwrap(), op2);
self.stack.push(res);
}
If you have an FnOnce, you can only call it when you have an owned value of it. If you need to call it when it is behind a reference, you have to take an Fn instead. In your case you probably want to make bin_op_on_stack accept F rather than &F.
thanks. I read that idiomatic rust never moves arguments into functions unless necessary. First, I had it as F then changed it to &F. The operators that are applied never change hence a reference would be a better choice and I would need to switch to Fn right?
No one may say whether it's better to accept &F (and ask for Fn) or F (and then permit FnOnce) by just looking on abstract bin_op_on_stack function in isolation.
Sometimes you may need to spend months experimenting to answer that… that's why I wouldn't worry too much about making the best choice on the first try: Rust if famous for robust refactorings and you need to know how bin_op_on_stack would be used to pick the right approach thus I would just pick side that you “feel” is right… and would be ready to change it later, if I would find out that my choice is bad.
Idiomatic rust should not force the caller to move data into a function unless necessary.
However by accepting a F where F: FnOnce(...) here you are not forcing someone to pass an owned function to it, because borrowed functions also implement FnOnce! A similar argument can be made for other traits like AsRef<str>.
Moreover note that FnOnce is less restricting for the caller than a Fn. With a Fn the caller is forced to pass a function that can possibly be called multiple times, and can do so without mutable access to its captures. Meanwhile FnOnce only requires it to be callable at most once, so the caller can e.g. consume/move data in it.