4

If I want a non-copyable type-erased (dynamically-typed) callable, that's

Box<dyn Fn(i32) -> ()>

If I want a ref-counted type-erased callable, that's (depending on whether I need thread-safety or not)

Rc<dyn Fn(i32) -> ()>
Arc<dyn Fn(i32) -> ()>

But here, the copies all refer to the same underlying memory - they're not distinct.

If I want distinct callable objects, how can I do that? Box<T> already implements Clone when T implements Clone, but Fn does not implement Clone so that doesn't apply here. Doing something like:

Box<dyn Fn(i32) -> () + Clone>

fails with:

error[E0225]: only auto traits can be used as additional traits in a trait object
 --> src/main.rs:7:35
  |
7 | fn foo(f: Box<dyn Fn(i32) -> () + Clone>) {
  |                   -------------   ^^^^^ additional non-auto trait
  |                   |
  |                   first non-auto trait
  |
  = help: consider creating a new trait with all of these as super-traits and using that trait here instead: `trait NewTrait: Fn<(i32,)> + Clone {}`
  = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

The suggestion in the error doesn't work because of the spelling of Fn, but this:

trait CopyableFn: Fn(i32) -> () + Clone {}
Box<dyn CopyableFn>

By itself also doesn't work because:

error[E0038]: the trait `CopyableFn` cannot be made into an object
 --> src/main.rs:7:11
  |
5 | trait CopyableFn: Fn(i32) -> () + Clone {}
  |       ----------                  ----- ...because it requires `Self: Sized`
  |       |
  |       this trait cannot be made into an object...
6 | 
7 | fn foo(f: Box<dyn CopyableFn>) {
  |           ^^^^^^^^^^^^^^^^^^^ the trait `CopyableFn` cannot be made into an object

Is there a way of creating a type object for Fn that is clonable such that the copies are distinct?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Barry
  • 286,269
  • 29
  • 621
  • 977
  • What is your goal in making the copies distinct? – Aplet123 Dec 08 '20 at 17:04
  • 5
    This is actually a duplicate of [How to clone a struct storing a boxed trait object?](https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object). The problem is that adding `Clone` as a supertrait to `CopyableFn` makes it no longer object safe (`dyn CopyableFn` becomes invalid). The solution (`clone_box`) explained in that answer will solve your problem – Ibraheem Ahmed Dec 08 '20 at 17:18
  • 4
    Here is an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=526fa5c419e5e1a1aa2f477833ca781e – Ibraheem Ahmed Dec 08 '20 at 17:26
  • @IbraheemAhmed Yeah I saw that question, but I can't exactly add a base trait to `Fn`. And your example isn't really `Fn` either - it's a different kind of calling. `f_clone(42)` does not work, you have to write `f_clone.call(42)`. – Barry Dec 08 '20 at 17:40
  • 2
    *but `Fn` does not implement `Clone`* — [you sure?](https://stackoverflow.com/a/27883569/155423) – Shepmaster Dec 08 '20 at 17:44
  • @Shepmaster [This doesn't work](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e00e7bbfc2f791aa344555d31a97fe78) though, which is how I thought things would work. – Barry Dec 08 '20 at 17:52
  • 4
    [Here is my example updated to use Fn](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9d8c628ee35065dcc177ed9ee394cc67). Does that work for you? If so, I think this should be marked as a duplicate – Ibraheem Ahmed Dec 08 '20 at 18:15
  • @IbraheemAhmed Yep. Thank you! – Barry Dec 08 '20 at 20:19

1 Answers1

6

Instead of having CloneableFn be a supertrait of Clone, implement a clone_box method that clones it into a box:

trait CloneableFn: Fn(i32) -> () {
    fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn>
    where
        Self: 'a;
}

Since unsized types like dyn CloneableFn cannot be cloned (Clone requires Sized), there's little reason to have Clone as a supertrait here. However, having Fn(i32) -> () as a supertrait allows the functions to be called as normal.

Then the CloneableFn can be implemented for all types that implement both Fn(i32) -> () and Clone:

impl<F> CloneableFn for F
where
    F: Fn(i32) -> () + Clone,
{
    fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn>
    where
        Self: 'a,
    {
        Box::new(self.clone())
    }
}

Finally, Box<dyn CloneableFn> does not automatically implement Clone since dyn CloneableFn doesn't, so we can implement that ourselves:

impl<'a> Clone for Box<dyn 'a + CloneableFn> {
    fn clone(&self) -> Self {
        (**self).clone_box()
    }
}

With this, you can now clone Box<dyn CloneableFn> and call it as a regular function:

// let closure borrow some shared state
use std::sync::atomic::{AtomicI32, Ordering};
let x = AtomicI32::new(0);

let f = |n| {
    println!("x = {}", x.fetch_add(n, Ordering::Relaxed));
};

let f: Box<dyn CloneableFn> = Box::new(f);
let g = f.clone();

f(3);
g(5);
f(7);

Here's a full playground example

This is related to How to clone a struct storing a boxed trait object?, but in that case the targeted trait (Animal) can be altered to have a supertrait, while in this case that's not possible (since the targeted trait is Fn(i32) -> ()). In a way, it's the opposite approach: adding a trait of which the target is a supertrait instead of adding a supertrait to the target.

Jan Hohenheim
  • 3,552
  • 2
  • 17
  • 42
Frxstrem
  • 38,761
  • 9
  • 79
  • 119