Cannot move a value of type FnOnce error when implementing callbacks

The main problem I am trying to solve is,

I have an implementation to read messages from stdin, parse them and call the appropriate handler. I have implemented send and send_sync. send sends the messages and responses are handled asynchronously by the stdin loop. send_sync waits for a response and returns it as Result<Message, Error>.

To implement send_sync, The program keeps a map of expected message id and callback fn in HashMap<i64, Arc<dyn Callback>> where Callback is, FnOnce(Message) -> Result<(), String> + Send + Sync + 'static.

I am using oneshot channels and the code looks like this,

let (tx,rx) = tokio::sync::oneshot::channel();
self.callbacks.insert(expected_msg_id, Arc::new(move |msg: Message| -> Result<(), String> {
    tx.send(msg);
    Ok(())
}));

I was using Fn instead of FnOnce but it complains closure implements FnOnce not Fn(which makes sense I thinkk, Is it because I am moving tx handle and it's dropped at the end?)

193 |         let func = move |msg: Message| -> Result<(), String> {
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `Fn`
194 |             tx.send(msg);
    |             -- closure is `FnOnce` because it moves the variable `tx` out of its environment
...
199 |             .insert(msg.body.msg_id.unwrap_or(0) + 1, Arc::new(func));
    |                                                       -------------- the requirement to implement `Fn` derives from here

So I changed the signature to FnOnce and now it says,

error[E0161]: cannot move a value of type `dyn FnOnce(Message) -> Result<(), std::string::String> + Send + Sync`
   --> src/lib.rs:123:37
    |
123 |                     if let Err(e) = callback(message) {
    |                                     ^^^^^^^^ the size of `dyn FnOnce(Message) -> Result<(), std::string::String> + Send + Sync` cannot be statically determined

error[E0507]: cannot move out of an `Arc`
   --> src/lib.rs:123:37
    |
123 |                     if let Err(e) = callback(message) {
    |                                     ^^^^^^^^---------
    |                                     |
    |                                     value moved due to this call
    |                                     move occurs because value has type `dyn FnOnce(Message) -> Result<(), std::string::String> + Send + Sync`, which does not implement the `Copy` trait

I am not sure how to fix this problem. Please let me know if there is a cleaner/easy way to solve the original problem or if there is a good way to solve this particular problem? Thank you

An minimum reproducible example on Rust Playground

  1. FnOnce needs ownership of its captures. Therefore you can't call an FnOnce behind indirection. So you'll need to stick with Fn(Mut).
  2. If you need to call consuming methods from an FnMut, then the standard trick is to put them behind an Option, and use Option::take to get the value out from behind the reference.
  3. This of course only works when you have a mutable reference. If you want to mutate stuff through a shared reference, then you'll need a shared mutability primitive; in a concurrent context, that's Mutex or RwLock.
  4. Finally, to make your function an Fn, you'll ned to use shared ownership (i.e., Arc) and clone the Arc.

All in all, here's a fix.

1 Like

Found the solution

I was using Arc because I assumed it would be required to share it across threads or call it from other threads.

Both problems are fixed if I use Box instead of Arc

Thank you, I understand this better now!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.