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:
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 . 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
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,
{}
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.