Passing various unboxed closures to function

I'm looking to create a function like this:

fn set_js_closure <T>(my_fn: T) -> Closure {
    let my_fn_boxed = Box::new(my_fn);
    Closure::wrap(my_fn_boxed) // or Closure::once if FnOnce
}

To use like this:

let cb = move || () /* varying closures */;
let cb_closure = set_js_closure(cb);
let cb_ref = cb_closure.as_ref().unchecked_ref();
// do stuff with closure
// do stuff with reference

I tried by using a function, and by using a struct+trait+impl combo, but anything I write breaks (even with generics) because the [traits/args/return-values/lifetimes] never match-up/apply for ALL the various closures that I pass.

Example attempts:
test v7 does not compile
test v8 compiles with generics but was unable to reuse the code with wasm::Closure methods.
test v9 does not compile because I cant use generics with "Box as"

I know I can just nest the box/closure statements (instead of using a function), or possibly create a macro to expand to all the different closure variants (suggested in IRC). But it's been bugging me for weeks that I'm unable to create a wrapper function for two statements.

What's the way to go about writing a wrapper function for unboxed closures? Thanks!

// Reference: closures I'm trying to pass:
//
// use wasm_bindgen::prelude::*;
// use web_sys::Event;
// function A
// with arg + return value
let cb = move |e: Event| -> Option<String> { /**/ };
let cb_boxed = Box::new(cb) as Box<dyn Fn(Event) -> Option<String>>;

// function B
// with arg
let cb = move |e: Event| { /**/ };
let cb_boxed = Box::new(cb) as Box<dyn FnMut(Event)>;

// function C
// as FnMut
let cb_1 = move || { /**/ };
let cb_1_boxed = Box::new(cb_1) as Box<dyn FnMut()>;
// as FnMut
let cb_2 = move || { /**/ };
let cb_2_boxed = Box::new(cb_2) as Box<dyn FnMut()>;
// as FnOnce + Closure::once + static
// calls: cb_1_closure, cb_2_closure
let cb_3 = move || { /**/ };
let cb_3_boxed = Box::new(cb_3) as Box<dyn FnOnce()>;

I guess from your post if you need something wasm_bindgen::Closure compatible. At a skim I believe that means it needs to be 'static and cannot return borrows from the arguments (and can only take so many arguments). Beyond that I'm not sure I really understand your goal.

So I'll just review your code and hopefully it's useful.


In v7, the trait doesn't really make sense for FnMut or FnOnce, as you need a &mut to call the former and you need ownership to call the latter. [1] But you can implement it for Wrap<'_, dyn Fn()> and similar. Note that everywhere you have a &dyn Fn() you're probably going to need to have a &(dyn Fn() + 'static) instead for the sake of wasm_bindgen::Closure.

Hard to diagnose the problem with v8 without knowing the errors you got.

The problem in v9 is that you're boxing &self and trying to say it's a Box<&T>. You need to box &self.value instead.


Other random comments:

  • To work generically with wasm_bindgen::Closure, you probably want traits that restrict yourself to exactly the types you can wrap and so on. Unfortunately, they've chosen to make that bound something you "shouldn't rely on" for some reason.

  • To be FnMut and not Fn, a closure needs to modified some captured state (and not just do something with an argument).

    let mut x = String::new();
    let f2 = move |c: char| { x.push(c); 0 };
    
  • To be FnOnce only, a closure needs to consume some captured state. Also, dropping integers doesn't consume them, because they are Copy.

    let s = String::new();
    let f3 = |x: i32| { drop(s); x };
    

  1. Your f2 and f3 also aren't limited like you've labeled them, but after casting to dyn FnMut or dyn FnOnce, that doesn't matter for the sake of illustration. ↩ī¸Ž

2 Likes

Thank you quinedot! I'm reading the all the playground changes you made to better understand what I need to do.

Thanks again for all the tips. The reason that I am using sample functions instead of source code is because wasm-bindgen is not in the list of crates included in the playground. So your corrections on my FnMut and FnOnce will be helpful in the future when trying to mock-up scenarios.

I did have a wrapper function working in the past, but only for one function/arg/return type. I went back and redid it for all the function/arg/return types -- to better illustrate what I was hoping to do.

The following is actual (shortened) code that compiles locally:

Given these three functions that require 5 closures...

use wasm_bindgen::{prelude::*}; // ...
use web_sys::Event; // ...
fn fn_1(...) {
    let cb_1 = move || { /**/ };
    let cb_1_closure = set_closure_fnmut(cb_1); // new
    // let cb_1_boxed = Box::new(cb_1) as Box<dyn FnMut()>; // old
    // let cb_1_closure = Closure::wrap(cb_1_boxed); // old

    let cb_2 = move || { /**/ };
    let cb_2_closure = set_closure_fnmut(cb_2); // new
    // let cb_2_boxed = Box::new(cb_2) as Box<dyn FnMut()>; // old
    // let cb_2_closure = Closure::wrap(cb_2_boxed); // old

    let cb_3 = move || {
        // cb_1_closure to reference
        // cb_2_closure to reference
        if ... {
            if ... {
                if ... {
                    // do stuff with cb_1 reference
                    cb_1_closure.forget();
                }
                // ...
            } else {
                // do stuff with cb_2 reference
                cb_2_closure.forget();
            }
        } else {
            ...
        }
    };
    let cb_3_closure = set_closure_fnonce(cb_3); // new
    // let cb_3_boxed = Box::new(cb_3) as Box<dyn FnOnce()>; // old
    // let cb_3_closure = Closure::once(cb_3_boxed); // old

    // Note: if I exclude the cb_1 & cb_2 forget statements, then cb_3
    // can use set_closure_fnmut() instead of set_closure_fnonce()

    // cb_3_closure to reference
    // do stuff with cb_3 reference
    cb_3_closure.forget();
}
fn fn_2(...) {
    let cb = move |e: Event| { /**/ };
    let cb_closure = set_closure_fnmut_arg(cb); // new
    // let cb_boxed = Box::new(cb) as Box<dyn FnMut(Event)>; // old
    // let cb_closure = Closure::wrap(cb_boxed); // old
    // ...
    // cb_closure to reference
    // do stuff with reference
    cb_closure.forget();
}
fn fn_3(...) {
    let cb = move |e: Event| -> Option<String> { /**/ };
    let cb_closure = set_closure_fn_arg_ret(cb); // new
    // let cb_boxed = Box::new(cb) as Box<dyn Fn(Event) -> Option<String>>; // old
    // let cb_closure = Closure::wrap(cb_boxed); // old
    // ...
    // cb_closure to reference
    // do stuff with reference
    cb_closure.forget();
}

I require four variants of a wrapper function...

fn set_closure_fnmut<T>(cb: T) -> Closure<dyn FnMut()>
where
    T: FnMut() + 'static,
{
    let cb_boxed = Box::new(cb) as Box<dyn FnMut()>;
    Closure::wrap(cb_boxed)
}

fn set_closure_fnonce<T>(cb: T) -> Closure<dyn FnMut()>
where
    T: FnOnce() + 'static,
{
    let cb_boxed = Box::new(cb) as Box<dyn FnOnce()>;
    Closure::once(cb_boxed)
}

fn set_closure_fnmut_arg<T>(cb: T) -> Closure<dyn FnMut(Event)>
where
    T: FnMut(Event) + 'static,
{
    let cb_boxed = Box::new(cb) as Box<dyn FnMut(Event)>;
    Closure::wrap(cb_boxed)
}

fn set_closure_fn_arg_ret<T>(cb: T) -> Closure<dyn Fn(Event) -> Option<String>>
where
    T: Fn(Event) -> Option<String> + 'static,
{
    let cb_boxed = Box::new(cb) as Box<dyn Fn(Event) -> Option<String>>;
    Closure::wrap(cb_boxed)
}

I did not go with this method even though it compiles because it's too much additional code. I kept everything inline (old method). Ideally, I would only have only one set_closure function for the 5 closures that need it. Something like the following...

/*
 * TYPEIN:
 * FnMut() + 'static,
 * FnOnce() + 'static,
 * FnMut(Event) + 'static,
 * Fn(Event) -> Option<String> + 'static,
 *
 * BOXTYPE:
 * dyn FnMut()
 * dyn FnOnce()
 * dyn FnMut(Event)
 * dyn Fn(Event) -> Option<String>
 *
 * TYPEOUT:
 * dyn FnMut()
 * dyn FnMut(Event)
 * dyn Fn(Event) -> Option<String>
 */
fn set_closure<T>(cb: T) -> Closure<TYPEOUT>
where
    T: TYPEIN + 'static,
{
    // even though generics are not allowed with 'as' statements.
    let cb_boxed = Box::new(cb) as Box<BOXTYPE>;
    Closure::wrap(cb_boxed)
}

Based on your playground changes in v7. I may try keep trying with generics to see what I can come up with, and also try to improve on the playground mockup.

I think your main problem is going to be coherence (not having overlapping implementations), because you want

  • T: FnMut() -> Box<dyn FnMut()> (via wrap)
  • T: FnOnce() -> Box<dyn FnMut()> (via once)

And those definitely overlap -- if something implements FnMut, it implements FnOnce.

Maybe consider making your own FnOnce to FnMut wrapper.

I think you can hit the other four cases with something like

trait SetClosure<Dyn: ?Sized> {
    fn set_closure(self) -> Closure<Dyn>;
}

impl<F, A, R> SetClosure<dyn FnMut(A) -> R> for F
where
    F: FnMut(A) -> R + 'static,
{
    fn set_closure(self) -> Closure<dyn FnMut(A) -> R> {
        let cb_boxed = Box::new(self) as Box<dyn FnMut(A) -> R>;
        Closure::wrap(cb_boxed)
    }
}

impl<F, A, R> SetClosure<dyn Fn(A) -> R> for F
where
    F: Fn(A) -> R + 'static
{
    fn set_closure(self) -> Closure<dyn Fn(A) -> R> {
        let cb_boxed = Box::new(self) as Box<dyn Fn(A) -> R>;
        Closure::wrap(cb_boxed)
    }
}

impl<F, R> SetClosure<dyn FnMut() -> R> for F
where
    F: FnMut() -> R + 'static,
{
    fn set_closure(self) -> Closure<dyn FnMut() -> R> {
        let cb_boxed = Box::new(self) as Box<dyn FnMut() -> R>;
        Closure::wrap(cb_boxed)
    }
}

impl<F, R> SetClosure<dyn Fn() -> R> for F
where
    F: Fn() -> R + 'static
{
    fn set_closure(self) -> Closure<dyn Fn() -> R> {
        let cb_boxed = Box::new(self) as Box<dyn Fn() -> R>;
        Closure::wrap(cb_boxed)
    }
}

Macro to reduce code left as an exercise.

I was able to use your suggestions and get something to compile locally:

For five closures:

  • Two traits
  • Three implementations
trait SetClosureFnonce<Dyn: ?Sized> {
    fn set_closure_fnonce(self) -> Closure<Dyn>;
}
// used for one closure
impl<F> SetClosureFnonce<dyn FnMut()> for F
where
    F: FnOnce() + 'static,
{
    fn set_closure_fnonce(self) -> Closure<dyn FnMut()> {
        let cb_boxed = Box::new(self) as Box<dyn FnOnce()>;
        Closure::once(cb_boxed)
    }
}
trait SetClosureFnMut<Dyn: ?Sized> {
    fn set_closure_fnmut(self) -> Closure<Dyn>;
}
// used for two closures
impl<F, A, R> SetClosureFnMut<dyn FnMut(A) -> R> for F
where
    F: FnMut(A) -> R + 'static,
    A: wasm_bindgen::convert::FromWasmAbi + 'static,
    R: wasm_bindgen::convert::IntoWasmAbi + 'static,
{
    fn set_closure_fnmut(self) -> Closure<dyn FnMut(A) -> R> {
        let cb_boxed = Box::new(self) as Box<dyn FnMut(A) -> R>;
        Closure::wrap(cb_boxed)
    }
}
// used for two closures
impl<F> SetClosureFnMut<dyn FnMut()> for F
where
    F: FnMut() + 'static,
{
    fn set_closure_fnmut(self) -> Closure<dyn FnMut()> {
        let cb_boxed = Box::new(self) as Box<dyn FnMut()>;
        Closure::wrap(cb_boxed)
    }
}
// Usage:

// let cb = move || { /**/ }; x2
// let cb = move |e: Event| { /**/ };
// let cb = move |e: Event| -> Option<String> { /**/ };
let cb_closure = cb.set_closure_fnmut();

// let cb = move || { /* contains forget() calls */ }
let cb_closure = cb.set_closure_fnonce();

I built up 4 separate traits and implemenations (for each closure type) and slowly tried to consolidate as much code as possible. For now, this is acceptable to me (as a learning exercise) even if the original inline way of creating js_closures is more concise.

One notable time-suck I enountered was this error:

error[E0599]: the method `set_closure` exists for closure `[closure@src/lib.rs:212:16: 214:6]`, but its trait bounds were not satisfied
   --> src/lib.rs:216:29
    |
212 |     let cb_1 = move || {
    |                -------
    |                |
    |                doesn't satisfy `<_ as FnOnce<(_,)>>::Output = _`
    |                doesn't satisfy `_: FnMut<(_,)>`
    |                doesn't satisfy `_: SetClosure<(dyn FnMut(_) -> _ + 'static)>`
...
216 |     let cb_1_closure = cb_1.set_closure();
    |                             ^^^^^^^^^^^ method cannot be called on `[closure@src/lib.rs:212:16: 214:6]` due to unsatisfied trait bounds
    |
    = note: `cb_1` is a function, perhaps you wish to call it
note: the following trait bounds were not satisfied because of the requirements of the implementation of `SetClosure<_>` for `_`:
      `<[closure@src/lib.rs:212:16: 214:6] as FnOnce<(_,)>>::Output = _`
      `[closure@src/lib.rs:212:16: 214:6]: FnMut<(_,)>`
      `<&[closure@src/lib.rs:212:16: 214:6] as FnOnce<(_,)>>::Output = _`
      `&[closure@src/lib.rs:212:16: 214:6]: FnMut<(_,)>`
      `<&mut [closure@src/lib.rs:212:16: 214:6] as FnOnce<(_,)>>::Output = _`
      `&mut [closure@src/lib.rs:212:16: 214:6]: FnMut<(_,)>`
   --> src/lib.rs:49:15
    |
49  | impl<F, A, R> SetClosure<dyn FnMut(A) -> R> for F
    |               ^

Lost hours adding-removing-breaking traits everywhere / trying to understand why/how my input closure needs to satisfy/have the output traits.

Ends up that the problem was -- I was using an implementation with a generic for the arguments (the [A] in the impl), with a closure that did not provide any arguments (e.g. move || { /**/ }). When I removed all the [A] references/generics, it compiled. However, the compiler did not complain if the closure did not have a return value but used an [R] generic. Super not-intuitive compiler error.

So that is why there is an implentation with an [A] generic for arguments, and another implementation without. Interestingly, The compiler did NOT complain about "conflicting implementations of trait" for the two [F: FnMut()] implementations, but did (as mentioned above) throw an error when trying to also implement for [F: FnOnce()], so that was put on a separate trait.

I think I'm done with this issue for now. I appreciate all the assistance!!

Found 4 more closures (TouchEvents) in another .rs file that can use the traits without issue or trait modification, so that's cool.

Basically, function argument types are input parameters to the Fn traits, while the return type is an associated type (an output of the Fn traits). Implementations "with no return type" like Fn(...) are sugar for Fn(...) -> ().

You can limit your implementation based on associated types (and must do so for the Fn traits on stable), but they don't factor into coherence / resolving an implementation. But dfferent input parameters result in distinct, coherent implementations. So Fn() doesn't collide with Fn(A). But Fn() collides with Fn() -> _ (whether generic or concrete).

Hence the impls that differ on arguments, but use a generic return type to cover more possibilities.

There's a bit more flexibility on unstable as you can fake variadic generics to some extent and omit the return type if you don't care about it.

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.