Generic Closure

Hello, I have a problem with the generic function.
The compiler suggests adding:

Closure: std::ops::Fn<(std::string::String, bool)> 
Closure: std::ops::Fn<(std::string::String, u8)>

It's weird because function is generic Closure: Fn(String, T) -> Result<(), String>.

My function:

pub fn create_cb_enum<T, Closure>(var_name: &String, var_type: &String, callback: Closure) -> Option<WriteCallbackClosure> 
where Closure: 'static + Sized + std::panic::UnwindSafe + std::panic::RefUnwindSafe + Send + Sync,
    T: Any,
    Closure: Fn(String, T) -> Result<(), String>, 
{
        let box_cb = Box::new(callback);
        
        if var_name == "BOOL"
        {
            return Some(WriteCallbackClosure::BOOL(box_cb));
        }
        else if var_name == "U8"
        {
            return Some(WriteCallbackClosure::U8(box_cb));
        }
        
        return None;
}

Full example: Playground

Please help.

Rust does not have generic closures, although there is a postponed RFC that would add them.

3 Likes

You can only create a type that can be called with multiple argument types on nightly. Closures can't be made generic. You can only do this by manually implementing Fn for a type, which is only possible on nightly.

1 Like

You can make this code work if you box the closure separately for each case and use something like Any::downcast_ref to convert the argument type from bool to T (for example): Playground

pub fn create_cb_enum<T, Closure>(var_name: &String, callback: Closure) -> Option<WriteCallbackClosure> 
where Closure: 'static + Sized + std::panic::UnwindSafe + std::panic::RefUnwindSafe + Send + Sync,
    T: Any + Copy,
    Closure: Fn(String, T) -> Result<(), String>, 
{
        fn convert<A:Any+Copy, B:Any+Copy>(x:A)->B {
            *(&x as &dyn Any).downcast_ref().unwrap()
        }

        if TypeId::of::<T>() == TypeId::of::<bool>()
        {
            let box_cb:BoolPtr = Box::new(move |s,x| callback(s, convert(x)));
            return Some(WriteCallbackClosure::BOOL(box_cb));
        }

        if TypeId::of::<T>() == TypeId::of::<u8>()
        {
            let box_cb:U8Ptr = Box::new(move |s,x| callback(s, convert(x)));
            return Some(WriteCallbackClosure::U8(box_cb));
        }
        
        return None;
}
1 Like

Indeed, if we look at your implementation, the comparisons on a given string hint at your needing dynamic dispatch, more precisely, dynamic type dispatch, which is achieved through the dyn Any trait object.


FWIW, I'll show other alternatives, since we don't know exactly what you are looking for.

Making the type of the closure depend on some given string

This is, for instance, if you really want your call site to look like:

    let f1 = move |name, state: bool| {
        println!("name: {}, state: {}", name, state);
        return Ok(());
    };
    let _v1 = create_cb_enum("BOOL", f1);
    
    let f2 = move |name, state: u8| {
        println!("name: {}, state: {}", name, state);
        return Ok(());
    };
    let _v2 = create_cb_enum("U8", f2);

Well, that cannot be achieved unless you use dyn-amic dispatch, as mentioned above, but if the strings that you were to give were always constant, in the future (currently nightly only) you should be able to write:

    let f1 = move |name, state: bool| {
        println!("name: {}, state: {}", name, state);
        return Ok(());
    };
    
    let _v1 = create_cb_enum::<_, "BOOL">(f1);
    
    let f2 = move |name, state: u8| {
        println!("name: {}, state: {}", name, state);
        return Ok(());
    };
    
    // let _v2 = create_cb_enum::<_, "BOOL">(f2); /* Error */
    let _v2 = create_cb_enum::<_, "U8">(f2);

Now, truth be told, we are just doing classic generics, i.e., static / compile-time dispatch, it's just that we are using string constants instead of types, hence all the nightly features involved to achieve that.

If we sacrifice the string types, one can very simply write:

    let f1 = move |name, state: bool| {
        println!("name: {}, state: {}", name, state);
        return Ok(());
    };
    
    let _v1 = create_cb_enum::<_, bool>(f1);
    
    let f2 = move |name, state: u8| {
        println!("name: {}, state: {}", name, state);
        return Ok(());
    };
    
    // let _v2 = create_cb_enum::<_, u8>(f1); /* Error */
    let _v2 = create_cb_enum::<_, u8>(f2);

Having "generic closures"

Closures in Rust involve the Fn... family of traits, which are just language-provided sugar for what at the end of the day is:

  1. just a very basic trait. This trait, for instance, does not support a generic parameter.

  2. a syntax to define an ad-hoc type that implements this trait, and then instance that type with the appropriate captures.


  1. The first point can thus be easily fixed by using a more complex trait:

    trait FnStringGenericSecondParam
    :   'static
        + Send + ::std::panic::UnwindSafe
        + Sync + ::std::panic::RefUnwindSafe
    {
        fn call<T : Any + Debug> (
            self: &'_ Self, // by &self access <-> Fn (&mut <-> FnMut, etc.)
            _: String,
            _: T,
        ) -> Result<(), String>
        ;
    }
    
    fn create_cb_enum (
        var_name: &str,
        callback: impl FnStringGenericSecondParam,
    ) -> Option<WriteCallbackClosure>
    {Some({
        match var_name {
            | "BOOL" => WriteCallbackClosure::BOOL(Box::new(
                move |s, b| callback.call(s, b)
            )),
            | "U8" => WriteCallbackClosure::U8(Box::new(
                move |s, u| callback.call(s, u)
            )),
            | "U16" => todo!(),
            | _ => return None,
        }
    })}
    
  2. For the second, let's see what we can achieve with user-defined syntax sugar, i.e., macros:

    let f1 = mk_closure!(<T> |name, state: T| {
        println!("name: {}, state: {:?}", name, state);
        return Ok(());
    });
    
    let v1 = create_cb_enum("BOOL", f1).unwrap();
    match v1 {
        | WriteCallbackClosure::BOOL(f) => {
            f("1".into(), false).unwrap();
        },
        | _ => unreachable!(),
    }
    
    let env = 42; // Let's try and add an environment to capture
    let f2 = mk_closure!([env: i32] <T> |name, state: T| {
        println!("name: {}, state: {:?}, env: {}", name, state, env);
        return Ok(());
    });
    
    let v2 = create_cb_enum("U8", f2).unwrap();
    match v2 {
        | WriteCallbackClosure::U8(f) => {
            f("2".into(), 27).unwrap();
        },
        | _ => unreachable!(),
    }
    
4 Likes

Thx for reply.
I would like change Fn() to FnMut() in your example.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=172f56021505b25aebfee42f63028ca0

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.