0

I'm trying to pass multiple closures to a struct and store them as boxed trait objects. These closures are wrapped in a thread-safe (Send, Sync) and clone-able (Clone) struct.

use std::thread;

// make trait alias
trait SafeFnMut: FnMut() + Send + Sync {}
impl<F> SafeFnMut for F where F: FnMut() + Send + Sync {}

#[derive(Clone, Debug)]
struct WithCall<F> where F: Sized {
    fp: Box<F>,
}

impl<F> WithCall<F> where F: SafeFnMut {
    // boxing the closure here
    pub fn new(fp: F) -> Self {
        WithCall { fp: Box::new(fp) }
    }

    pub fn run(&mut self) {
        (self.fp)()
    }
}

struct HasWithCall<T> where T: SafeFnMut {
    pub first_fn: Option<Box<WithCall<T>>>,
    pub second_fn: Option<Box<WithCall<T>>>,
}

fn main() {
    let mut first_fn = WithCall::new(|| {
        println!("Called!")
    });

    let mut second_fn = WithCall::new(|| {
        println!("Called other!")
    });

    let has_with_call = HasWithCall {
        first_fn: Some(Box::new(first_fn.clone())),
        second_fn: Some(Box::new(second_fn.clone()))
    };

    println!("{:?}", first_fn.run());

    let mut first_fn_a = first_fn.clone();
    let mut first_fn_b = first_fn;

    let a = thread::spawn(move || {
        println!("In remote thread: {:?}", first_fn_a.run());
    });

    let b = thread::spawn(move || {
        println!("In remote thread: {:?}", first_fn_b.run());
    });

    a.join().expect("Thread A panicked");
    b.join().expect("Thread B panicked");
}

This code gives me the following error:

error[E0308]: mismatched types   --> src/main.rs:39:34
second_fn: Some(Box::new(second_fn.clone()))                   
                         ^^^^^^^^^^^^^^^^^ expected closure, found a different closure

note: expected type `WithCall<[closure@src/main.rs:29:38: 31:6]>`
              found type `WithCall<[closure@src/main.rs:33:39: 35:6]>`

note: no two closures, even if identical, have the same type

help: consider boxing your closure and/or using it as a trait object

I've referenced this question for the thread-safe closures and this question for the closure type error.

I see that there are other options like a reference to a trait object or a function pointer. However, I'd like to solve this using a boxed trait object as I've written above.

Jmb
  • 18,893
  • 2
  • 28
  • 55
lovelikelando
  • 7,593
  • 6
  • 32
  • 50
  • [The clone is irrelevant to the error.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b2e89f005b77db0ee081ff3f1a98f099) I'm unclear on what you're trying to achieve, and why you have `F: Sized` in `WithCall` -- `Sized` is the default, you only need to name it to opt out. What are you trying to do? – trent Oct 03 '19 at 18:47
  • Ah, I appreciate the simplified example. So I really must be missing something simple between say [this example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=90d137a201e945a1267eb7750e8c1d93) using `Box` to get around the closure error and what I'm doing... – lovelikelando Oct 03 '19 at 18:51
  • To clarify the intent here, mutable closures are created elsewhere in my application. Those then need to be shared with another thread which invokes them. – lovelikelando Oct 03 '19 at 18:58
  • The difference that first leaps out at me is that that example is using `Box` (for which `Box` is a deprecated alias) inside the struct `B` while your `WithCall` is generic over `F`. Generics are resolved at compile time; `dyn` is resolved at runtime. If you want to use `first_fn` and `second_fn` interchangeably, you need `dyn`, which probably requires some extra work. If you just want to put them both in the struct, you can [give it an extra type parameter](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=562b55314a9fcb7e2da5fb4d6cf3cc0c). – trent Oct 03 '19 at 19:04
  • Is `dyn`amic dispatch required for your problem, or does that answer the question? – trent Oct 03 '19 at 19:08
  • Using extra type parameters works pretty well! I think that'll do it. Is that an anti-pattern? – lovelikelando Oct 03 '19 at 19:26
  • 1
    Not an antipattern. It might get a bit out of hand if you have 10 different closure types but 2 or 3 is normal. I'll write up an answer. – trent Oct 03 '19 at 19:30

1 Answers1

1

The error message in Expected closure, found a different closure happens because Vec<T> can only hold closures of one concrete type T. In your case, it looks easier to add a second type parameter U, so that you can hold two different closures of different types:

struct HasWithCall<T, U>
where
    T: SafeFnMut,
    U: SafeFnMut,
{
    pub first_fn: Option<Box<WithCall<T>>>,
    pub second_fn: Option<Box<WithCall<U>>>,
}

(full example)

As a side note, the extra Box seems unnecessary to me; it's probably sufficient to use Option<WithCall<T>> since there is a Box inside WithCall.

trent
  • 25,033
  • 7
  • 51
  • 90