Async callback with Serde Deserialize (Zero-copy)

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