I'm trying to use the tokio and futures library for some event driven processing but hitting some issues with reference duplication. All the examples I've seen are fairly simple and include a single future type with a single call back. Does anyone have examples of the right way to mix multiple future types with multiple callbacks that operate on the same data? I can't figure out how to do it without breaking ownership/borrowing rules. I could use an Arc<Mutex<...>> but that seems like wasteful overkill for an event loop on a single thread.
For the situation you are describing,
Arc is probably the best choice unless you actually know it is a performance problem.
Arc makes your futures
Send, which allows you to use the very convenient
The slightly faster solution is to use
Rc, but then instead of using
boxed() you have to do the less ergonomic
Box::new() around your future, and use
Box<Future<Item = T, Error = E>> instead of
BoxFuture<T, E>, also less ergonomic. However I don't think the upgrade is worth it unless you actually know the atomic counter is causing slowdown.
Rc are both shared ownership, and they're needed because the futures all need to have ownership of the data they're sharing - you don't know which future will complete last, so its not clear which one can 'own' the data and which one can just 'borrow' it.
The overhead of shared ownership is not usually a big deal as long as you only create the Rc or Arc once, at the time you create the event loop. Incrementing the reference count is pretty cheap. The expensive part is allocating the data in the heap, instead of on the stack (which happens when you call
Arc::new), but once you've done that its not a big problem.
A final alternative, which avoids that heap allocation, is to have the data owned outside the event loop, and then pass references to that data into the futures that run on the event loop. This is much more difficult because those futures are no longer
'static, instead they have to have a lifetime threaded through them. You also need to make sure the data outlives the event loop, which can be tricky.
So each of these steps gets faster at the cost of ease of use. I really recommend using
Arc and just making sure you don't create a new Arc for each new future, only clone it for each new future. That should probably be plenty fast. If not you can try advancing to faster architectures.
Thanks for your reply!
Arc and Rc don't work because I need mut access to the shared state. I wasn't objecting to using the Rc and Arc, but rather to using the Mutex when it seems like I shouldn't have to.
Here's a better representation of what I'm trying to do:
imagine and event loop with an integer counter and two unsync mpsc channels. If I receive a message on the first channel, I want to increment the counter, a message on the second channel and I want to decrement the counter. I cannot have futures that both have mut ownership of the counter right?
Even with the more difficult solution of borrowing references within the futures wouldn't work because I'd need multiple mutable borrows of the same data.
This seems like it would be a common problem with futures so I feel like I must not understand correctly how to use them.
If you just want a shared counter you can use an
Arc<AtomicUsize>, which can be mutated even if you have an immutable reference.
Alternatively, if your futures are all in the same thread, you can use the single-threaded equivalent of
Ahh, yes, RefCell is the thing I was missing! I can see how that will work now.
I read about RefCells way back when first learning Rust, but didn't see how they'd be useful so ignored and forgot about them.
This is much more difficult because those futures are no longer
'static, instead they have to have a lifetime threaded through them.
As it is, Tokio has rather limited for support for this. You can run a “main” future with arbitrary lifetimes, but you can’t spawn “auxiliary” futures because
Core has no lifetime parameter.
If only the
Future interface supported a “current state” parameter of some arbitrary associated type, then one could share a
&mut with multiple futures on a single-threaded executor!
Futures is not 1.0 yet. Have you suggested such a feature?
Since you're committing yourself to a single threaded executor you could use TLS for this.
Perfect, thanks :). Just wanted to make sure that this doesn't get lost in a sidecomment!
I must admit that I can't quite understand the issue in full, I think an example for the feature and one describing the impact on current existing code would help.