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:
-
just a very basic trait. This trait, for instance, does not support a generic parameter.
-
a syntax to define an ad-hoc type that implements this trait, and then instance that type with the appropriate captures.
-
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,
}
})}
-
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!(),
}