Async callback with Serde Deserialize (Zero-copy)

I'm trying the following implementation, assuming that I will receive values through a callback from Websockets.

(Playground) this is worked.

The requirements are as follows.

  • async callback
  • Deserialize without copy(Serde calls it Zero-copy deserialization)

Then, I wanna add async block to callback.

#[tokio::main]
async fn main() -> Result<()> {
    let _ = run(|res| async { // <- Add `async` here!! 
        println!("{:?}", res);
    })
    .await;

    Ok(())
}

I tried this.

// Callback trait
pub trait Callback<A>: for<'any> FnMut(A::V<'any>) -> Future<Output = ()> + Send
where
    A: Arg,
{
}

impl<A, F> Callback<A> for F
where
    A: Arg,
    F: for<'any> FnMut(A::V<'any>) -> Future<Output = ()> + Send,
{
}

Got err.

Compiling playground v0.0.1 (/playground)
error[E0782]: trait objects must include the `dyn` keyword
  --> src/main.rs:58:55
   |
58 | pub trait Callback<A>: for<'any> FnMut(A::V<'any>) -> Future<Output = ()> + Send
   |                                                       ^^^^^^^^^^^^^^^^^^^
   |
help: add `dyn` keyword before this trait
   |
58 | pub trait Callback<A>: for<'any> FnMut(A::V<'any>) -> dyn Future<Output = ()> + Send
   |                                                       +++

error[E0782]: trait objects must include the `dyn` keyword
  --> src/main.rs:67:39
   |
67 |     F: for<'any> FnMut(A::V<'any>) -> Future<Output = ()> + Send,
   |                                       ^^^^^^^^^^^^^^^^^^^
   |
help: add `dyn` keyword before this trait
   |
67 |     F: for<'any> FnMut(A::V<'any>) -> dyn Future<Output = ()> + Send,
   |                                       +++

For more information about this error, try `rustc --explain E0782`.
error: could not compile `playground` due to 2 previous errors

I may not have a good understanding of HRTB (Higher-Rank Trait Bounds.

How to add Future<Output = ()>?

First try: Hide the Future type

pub trait Callback<A>: for<'any> FnMut(A::V<'any>) -> Self::Fut
where
    A: Arg,
{
    type Fut: Future<Output = ()> + Send;
}

Rust Playground

Unfortunately, it hits the common pitfall of async callbacks.

error: lifetime may not live long enough
  --> src/main.rs:76:23
   |
76 |       let _ = run(|res| async move {
   |  __________________----_^
   | |                  |  |
   | |                  |  return type of closure `[async block@src/main.rs:76:23: 78:6]` contains a lifetime `'2`
   | |                  has type `Response<'1>`
77 | |         println!("{:?}", res);
78 | |     })
   | |_____^ returning this value requires that `'1` must outlive `'2`

See this post FYI: How to express that the Future returned by a closure lives only as long as its argument?

Second try: Use Box::Pin to connect the lifetime relation between inputs and the output

Rust Playground

error[E0582]: binding for associated type `Output` references lifetime `'any`, which does not appear in the trait input types
  --> src/main.rs:57:55
   |
57 | pub trait Callback<A>: for<'any> FnMut(A::V<'any>) -> BoxFuture<'any, ()>
   |                                                       ^^^^^^^^^^^^^^^^^^^
error[E0582]: binding for associated type `Output` references lifetime `'any`, which does not appear in the trait input types
  --> src/main.rs:66:39
   |
66 |     F: for<'any> FnMut(A::V<'any>) -> BoxFuture<'any, ()>
   |                                       ^^^^^^^^^^^^^^^^^^^

Well, Rust doesn't allow the HRTB here.

Third try: Put the lifetime on the trait

pub trait Callback<'any, A>: FnMut(A::V<'any>) -> BoxFuture<'any, ()>
where
    A: Arg + 'any,

Rust Playground

It works.

Bonus: Relax the lifetime bound on Arg::V<'a> where Self: 'a

pub trait Callback<'arg, A>: FnMut(A::V<'arg>) -> BoxFuture<'arg, ()>
where
    A: Arg,

Rust Playground

Retro: Apply the same trick to the first try

Rust Playground

error: implementation of `Callback` is not general enough
  --> src/main.rs:74:13
   |
74 |       let _ = run(|res| async move {
   |  _____________^
75 | |         println!("{:?}", res);
76 | |     })
   | |______^ implementation of `Callback` is not general enough
   |
   = note: `[closure@src/main.rs:74:17: 74:22]` must implement `Callback<'0, AbstructStruct>`, for any lifetime `'0`...
   = note: ...but it actually implements `Callback<'1, AbstructStruct>`, for some specific lifetime `'1`

It only works for async functions :slight_smile: [1]


Update: I've added the code here as an example of my crate async_closure.

You don't have to

  • define your Callback trait
  • or Box the return Future
  • or worry about captured variables with references

but I don't recommend using it.


  1. which is known for no reason, Lifetime bounds to use for Future that isn't supposed to outlive calling scope - #3 by steffahn ↩ī¸Ž

2 Likes

Wow! Great.
It may take me some time to understand it, but I would like to take the time to understand it.
Thank you for offering a solution! :smile: