I want to write a functions that can accept three callback types, like so:
pub enum Callback<T> {
Regular(Box<dyn FnMut(T) + Send>),
Boxed(Box<dyn FnMut(Box<T>) + Send>),
RegularWithInt(Box<dyn FnMut(T, i32) + Send>),
}
But the caller of the function should not need to use the Callback
enum, but simply be allowed to pass in a function or closure. This is what I came up with, taking some inspiration from Bevy's system functions:
// Helper trait for IntoCallback.
//
// For each tuple of args, it provides conversion from a function with
// these args to the correct enum variant.
trait ArgTuple<T> {
type Func;
fn convert(func: Self::Func) -> Callback<T>;
}
impl<T> ArgTuple<T> for (T,) {
type Func = Box<dyn FnMut(T) + Send>;
fn convert(func: Self::Func) -> Callback<T> {
Callback::Regular(func)
}
}
impl<T> ArgTuple<T> for (Box<T>,) {
type Func = Box<dyn FnMut(Box<T>) + Send>;
fn convert(func: Self::Func) -> Callback<T> {
Callback::Boxed(func)
}
}
impl<T> ArgTuple<T> for (T, i32) {
type Func = Box<dyn FnMut(T, i32) + Send>;
fn convert(func: Self::Func) -> Callback<T> {
Callback::RegularWithInt(func)
}
}
pub trait IntoCallback<T, Args>: Send {
fn into_callback(self) -> Callback<T>;
}
impl<T, A0, Func> IntoCallback<T, (A0,)> for Func
where
Func: FnMut(A0) + Send + 'static,
(A0,): ArgTuple<T, Func = Box<dyn FnMut(A0)>>,
{
fn into_callback(self) -> Callback<T> {
<(A0,) as ArgTuple<T>>::convert(Box::new(self))
}
}
impl<T, A0, A1, Func> IntoCallback<T, (A0, A1)> for Func
where
Func: FnMut(A0, A1) + Send + 'static,
(A0, A1): ArgTuple<T, Func = Box<dyn FnMut(A0, A1)>>,
{
fn into_callback(self) -> Callback<T> {
<(A0, A1) as ArgTuple<T>>::convert(Box::new(self))
}
}
However, rustc
informs me that the callbacks I thought would implement IntoCallback
don't in fact do so.
#[test]
fn callback_conversion() {
let cb = |s: String| {};
assert!(matches!(cb.into_callback(), Callback::Regular(_))); // Compiler error, "perhaps you need to implement IntoCallback?"
}
Where is the error? Is there a trick to get rustc
to explain why this trait is not implemented? I already checked that (String,)
implements ArgTuple<String>
, as expected.