Async “await this future but hang on to its output for later use”?

Is there any library which provides a data type with the following functionality?

  • owns either a Future or its Output value
  • can be asked to await the future if needed, then return a reference to the output

Its API would be like:

pub enum RetainedOutput<F: Future> {
    Pending(F),
    Ready(F::Output),
}
impl<F: Future> RetainedOutput<F> {
    pub fn new(future: F) -> Self {...}
    pub async fn get(self: Pin<&mut Self>) -> &F::Output {...}
    pub fn try_get(&self) -> Option<&F::Output> {...}
}

This is similar to other library types I’m aware of, but different from all of them:

  • It is similar to MaybeDone, but has no Gone state.
  • It is similar to tokio::sync::OnceCell, but has no not-yet-initialized state.
  • Shared can be convinced to behave like this (shared.clone().await) but would require a clonable output rather than returning references, so you end up with two heap allocations (presuming the output is wrapped in Arc) instead of zero. Shared is the right answer for accessing such a future-or-output from multiple tasks, though.
  • It needs no internal synchronization (unlike OnceCell and Shared, or an async lazy cell, do), since its state transition is guarded by &mut access, and therefore can be provided in platform-independent executor-independent no_std form.

I’ve encountered problems shaped somewhat like this a couple times and it feels like a missing basic abstraction, so I’m wondering if someone has already written it under a name I haven’t thought of.

You can copy'n'paste MaybeDone and delete the case you don't want.

BTW: fn get(&mut _) -> &_ APIs have an unfortunate effect of borrowing the result exclusively, despite that being a shared reference. It would need get().await; try_get().unwrap() to get a real shared reference.

Splitting it into two operations doesn’t have any benefits unless you are planning to .try_get().unwrap() more than once after the .get(). If you aren’t going to do that, then since you need exclusive access to be able to run .get().await at all, and there aren’t any other fields to borrow-split, I don’t see any advantage to releasing the exclusive borrow.

2 Likes