How to handle Clone macro when one of the fields is a function?

I have a struct and I am trying to add new, non-data fields to it. Map Transaction -> Callback and vec of Callback. I only vaguely understand what Clone is for, so I can't make a judgement call on if I should even try to resolve this issue or ditch the Clone.

So, how does one implement Clone on a vec<FnOnce> ?

error[E0277]: the trait bound `dyn for<'r> FnOnce(&'r block::Transaction): Clone` is not satisfied
   --> src/blockchain/src/
100 | #[derive(Serialize, Deserialize, Clone)]
    |                                  ----- in this derive macro expansion
103 |     pub trans_observers: HashMap<&'lt Transaction, OnTransactionSettled>,
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `dyn for<'r> FnOnce(&'r block::Transaction)`

#[derive(Serialize, Deserialize, Clone)]
pub struct Blockchain <'lt> {
    pub trans_observers: HashMap<&'lt Transaction, OnTransactionSettled>,
    pub block_observers: Vec<OnBlockEvent>,
    pub genesis_block: GenesisBlock,
    pub blocks: Vec<Block>,

If you don't need to Clone it, don't derive the Clone. Cloning a closure that can only otherwise be called once seems like a weird operation.

If you do need to clone it, you're probably better off with a Arc<Mutex<dyn FnMut ...>> or the like -- some sort of Clone supporting shared-ownership wrapper around the closures. But FnOnce won't play great with the shared ownership. You could have an Option<Box<dyn FnOnce>> in there I suppose, to keep it only callable once (across all clones). Or swap out the dyn FnOnce with a no-op closure or other default action I guess.

Otherwise you're probably looking at some custom type or trait to support both performing the Fn* operation and providing some clone-like but type-erased operation.

I was implementing your first suggestion when when your message came in - that solves that problem but, in true rust fashion, instigates 100 more.

If you're like to come show a bunch of rookies how it's done, we have an OSF project under development called pyrsia - it's meant to be a dapp, blockchain based artifact manager.

Store the callbacks in Arc<dyn Fn(…)>. This makes them cloneable.

So, let's conclude you need to clone these collections of FnOnce callbacks in some sense after all. You'll have to decide what should happen when you have a bunch of clones of them. I suspect you don't actually want FnOnce, but just to brain storm...

  • Each callback runs once, in the original struct, and not in any clones
    • When you make a clone, make it with an empty Vec
    • Maybe newtype the Vec of callbacks to do so
  • Each callback runs once, and they're split up between the original and the clone when cloning
    • Similar to above, but you'll need some sort of interior mutability like a Mutex to be able to alter the Vec during the clone
  • Each callback runs once, in whichever clone or original gets around to it first
    • Arc<Mutex<Option<Box<dyn FnOnce>>>> or something, probably new-typed. take() the Option and run the closure if the result is not None.
  • Each callback runs once when everything shuts down
    • Probably some sort of new type in an Arc with a destructor, and hope everything shuts down smoothly etc. Consider separating out a factory instead.
  • Each callback is in fact cloneable; each clone runs once
    • Don't do this one, it's not really what FnOnce is about and trying to force it would be messy. I can't think of this actually meaning anything other than "FnOnce isn't necessary", so use FnMut or Fn instead. Closure writers who need to can turn a FnOnce + Clone into a FnMut (and often Fn) themselves anyway. [1]
  • FnOnce isn't necessary, and these can be FnMut; each cloned struct calls the same set of FnMuts
    • Use Arc<Mutex<dyn FnMut>> or the like
  • FnMut isn't necessary either, and these can be Fn; each cloned struct calls the same set of Fns
    • Use Arc<dyn Fn> or the like

The last two are the typical approaches. FnOnce is about things you really only intend to call once; it's a bit odd to be carrying one around in something you're cloning.


  1. You could probably technically do it with a custom subtrait that has FnOnce as a super trait but also the ability to clone in a type erased manner some how. But don't. ↩︎

  2. And thank you for the invite, but my employment situation precludes it. ↩︎

1 Like

The scenario is this:

For the lifetime of the app, there shall only ever be one running instance of our blockchain. When a transaction settles (which can only occur once), then I want to invoke the callback supplied when the transaction was submitted, and that's it. Done. So, putting rust grammar/syntax aside, that callback will only ever be called once, and both the transaction and the function will only be used once, and thus why I use drain on the map when iterating.

Where it is not getting complicated is with cloning, which is an additional burden being placed on me by rust. I don't ever want or expect to make a copy of this blockchain struct. It's a singleton (which seems to be a very dirty word in rust). Hence, clone is basically an unnecessary complexity.

Thanks for the help

Can you elaborate a bit on what led you to believe this? Making only a single instance when the program starts and then passing around references or Arcs is usually a viable strategy. Not implementing Clone is then a safety against accidentally having multiple instances when you only ever want one.

Sounds like you should track down what is requiring Clone then. Rust imposes no inherent requirement that structs are Clone. If removing the derive breaks things, something is requiring it somewhere, either by trying to .clone() a known type or by having a : Clone bound.

1 Like

I figured it out. I guess what I was trying to decide is if it was even necessary - my understanding is that clone is like a deep copy where as Copy is just a shallow memcpy.

You guys did point me in the right direction though, so thanks

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.