Announcing reusable-box-future: A reusable `Pin<Box<dyn Future<Output = T> + Send>>`

repo: https://github.com/oblique/reusable-box-future
crate: https://crates.io/crates/reusable-box-future
docs: https://docs.rs/reusable-box-future

ReusableBoxFuture can be used when you need to dynamically dispatch a future multiple times but you want to avoid extra allocations. A very good case is when you need to compose an async fn in a Future trait implementation.

This code was originally written by @alice in tokio-util crate. The main reason that I extracted it was to be used by non-tokio users.

Some improvements over tokio-util's implementation:

3 Likes

Nice, a few comments :slightly_smiling_face::

  • You could try using a macro or include!-shenanigans (or generics with an abstraction over autotraits such as Send) to avoid code duplication between the Local (no Send bounds) and non-Local variants, provided the code remains readable :grinning_face_with_smiling_eyes:. By avoiding the code duplication, any future updates / fixes will be easier to apply.

  • consider adding a lifetime parameter 'a to generalize over the current 'static (in a first pass, you can replace all elided 'statics with explicit ones; elision & inference ought to be avoided when writing unsafe code). Granted, most executors out there require 'static Futures, but we never know :wink:

  • Finally, when writing impls "because Pin<Box<dyn Future...>> does the same", a very neat thing to do is to write an "explicit delegation to that property" (since code > documentation)

      // The future stored inside ReusableBoxFuture<T> must be Send.
      unsafe impl<T> Send for ReusableBoxFuture<T>
    + where
    +     Pin<Box<dyn 'static + Send + Future<Output = T>> : Send,
      {}
      
      // Just like a Pin<Box<dyn Future>> is always Unpin, so is this type.
      impl<T> Unpin for ReusableBoxFuture<T>
    + where
    +     Pin<Box<dyn 'static + Send + Future<Output = T>> : Unpin,
      {}
    
1 Like

Thanks for the comments!

I was also thinking to use a macro to avoid code duplication but in the end the code can become a bit more difficult to read. I will think about it again.

About the lifetime, the only drawback that I see when using 'a is that the new future needs to have the same lifetime as the one in constructor. So the following will fail to compile:

        let x1 = 1;

        let mut fut = ReusableBoxFuture::new(async {
            println!("{}", &x1);
        });

        let x2 = 2;

        fut.set(async {
            println!("{}", &x2);
        });

Producing a difficult to understand error:

error[E0597]: `x2` does not live long enough
   --> src/box_future.rs:281:29
    |
280 |           fut.set(async {
    |  _______________________-
281 | |             println!("{}", &x2);
    | |                             ^^ borrowed value does not live long enough
282 | |         });
    | |_________- value captured here by generator
283 |       }
    |       -
    |       |
    |       `x2` dropped here while still borrowed
    |       borrow might be used here, when `fut` is dropped and runs the `Drop` code for type `box_future::ReusableBoxFuture`
    |
    = note: values in a scope are dropped in the opposite order they are defined

I can not decide if it worth it or not, but I understand the need.

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.