Store async callback with reference argument without Box::pin from user

Greetings!

I know this question already pissed everyone, but still I can't figure out what to do.

I've tried to use that:

use std::{future::Future, pin::Pin};

struct Callback<A, B> {
    cb: Box<dyn for<'a> Fn(&'a A, B) -> Pin<Box<dyn Future<Output = ()> + 'a>>>,
}

impl<A, B> Callback<A, B> {
    fn new<F, Fut>(callback: F) -> Self
    where
        F: Fn(&A, B) -> Fut + 'static,
        for<'any> Fut: Future<Output = ()> + 'any,
    {
        Self {
            cb: Box::new(move |a, b| Box::pin(callback(a, b))),
        }
    }

    async fn call(&self, a: &A, b: B) {
        (self.cb)(a, b).await
    }
}

async fn concat(name: &String, id: usize) {
    println!("{name}{id}");
}

fn main() {
    let _cb = Callback::new(|name, id| concat(name, id));
}

And obviously got lifetime may not live long enough error.
Of course, I can ask user to return Box<Pin<T>> directly:

<...>
    fn new<F>(callback: F) -> Self
    where
        for<'a> F: Fn(&'a A, B) -> Pin<Box<dyn Future<Output = ()> + 'a>> + 'a,
    {
        Self {
            cb: Box::new(move |a, b| callback(a, b)),
        }
    }

<...>

    let _cb = Callback::new(|name, id| Box::pin(concat(name, id)));

but this is very inconvenient for user which will use my library.

So, any way to win battle with rust life-bound checker? Maybe some GAT magic?

I don't have a solution, but in case it's useful in seeing the problem, here:

impl<A, B> Callback<A, B> {
    fn new<F, Fut>(callback: F) -> Self
    where
        F: Fn(&A, B) -> Fut + 'static,

That can't work as type parameters like Fut have to be a single type, where as you're really looking for a

F: 'static + Fn(&A, B) -> impl Future + 'a

where the return type is parameterized by the input type's lifetime.

You can work around this shortcoming of the standard traits (albeit painfully)...

trait IntoCallback<A, B>: Sized {
    fn into_callback(self) -> Box<dyn for<'a> Fn(&'a A, B) -> Pin<Box<dyn Future<Output = ()> + 'a>>>;
}

trait IntoCallbackBootstrap<'a, A, B>
where
    A: ?Sized + 'a,
    Self: Fn(&'a A, B) -> Self::Fut,
{
    type Fut: Future<Output = ()> + 'a;
}

impl<'a, A, B, C, R> IntoCallbackBootstrap<'a, A, B> for C
where
    A: ?Sized + 'a,
    R: Future<Output = ()> + 'a,
    C: Fn(&'a A, B) -> R,
{
    type Fut = R;
}

impl<A, B, C> IntoCallback<A, B> for C
where
    C: for<'any> IntoCallbackBootstrap<'any, A, B> + 'static
{
    fn into_callback(self) -> Box<dyn for<'a> Fn(&'a A, B) -> Pin<Box<dyn Future<Output = ()> + 'a>>> {
        Box::new(move |a, b| Box::pin(self(a, b)))
    }
}

impl<A, B> Callback<A, B> {
    fn new<F>(callback: F) -> Self
    where
        F: IntoCallback<A, B>,

...but then I couldn't get your closure to be recognized as a higher-ranked closure (a known Rust weakness), and the opaque nature of compiler-generated Futures stymies the normal workarounds to this problem.

Users would have to write functions, not closures.


Maybe someone else can push it forward.

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.