Async: storing dynamic Future objects

Hey all,
I am trying to implement an async cache that is designed to store future values that multiple readers can poll. For that, the cache holds an internal map of future values. For polling by multiple users, I wanted to use Shared futures. The loader given to the cache is a function it uses to load missing/expired entries.
Sometimes the cache should chain some stuff on the futures it holds (1 example is to report statistics of the time it took to hold the value). This changes the stored type and throws a cryptic error.

By this point, I have already learned that an indirection using a trait object is required here. So the internal map is:

RwLock<HashMap<K, Entry<V, Pin<Box<dyn Future<Output = V>>>>>>
    = note: expected struct `Shared<Pin<Box<(dyn futures::Future<Output = V> + 'static)>>>`
               found struct `Shared<futures::future::Inspect<Fut, [closure@src/lib.rs:92:26: 97:18]>>`

code is here. Sorry for the length. most of it is type declerations.

  1. How did the compiler infer a Shared<Pin<Box<_>>>?
  2. Should I change the type of the loader to be F: Fn(&K) -> Fut and Fut: Box<dyn Future<Output = V>>? Should this create other problems?
  3. Is there a shorter or more idiomatic way to write this?

You need to be boxing it here:

let shared_fut = (self.loader)(k)
    .inspect(|_| {
        // self.stats
        //     .clone()
        //     .record_load_success((Instant::now() - start).as_nanos());
        println!("update stats load time: {:?}", start);
    })
    .boxed()
    .shared();

This is using the boxed() combinator from FutureExt. This will fail about errors talking about Send, but you can fix them by adding + Send to your boxed futures.

1 Like

@alice you are a lifesaver.
The compiler just sent me to throw Send on a few places. and then, after uncommenting the code for the statistics the borrow checker now complains about the borrowing of self. By this point, throwing a Static lifetime everywhere looks wrong to me.
Updated code in the playground.

I'm not seeing the updated playground?

Indeed, when the compiler suggests adding 'static, this is virtually always the wrong solution.

Right! Sorry for that. a new link...

That link is still identical to other playground, and still is missing the .boxed() call.

so weird. sorry. another one.

It is because the reference to self, which is stored inside shared_fut, ends up not only in the return value, but also in the map argument. My expectation is that you will have a lot of trouble getting that to work with an ordinary, and that you should figure out a way to only put owned data into the inspect future.

E.g. you could share the stats via an Arc.

1 Like