0

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.

nnnmmm
  • 7,964
  • 4
  • 22
  • 41
  • Commenting out everything that is not relevant for the test gives you a more useful error message: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cc795a4889044e8670de12e9709d71cc – Finomnis Sep 13 '22 at 19:41
  • 1
    Ahh, I need to add a Send trait there. Thank you! – nnnmmm Sep 13 '22 at 19:42

0 Answers0