Expand description
§aselect
Wait on multiple branches, without canceling or starving any futures, while allowing
safe access to mutable state. Works in #[no_std], allocates no memory, and has no
non-optional dependencies. Tested with miri.
§Background
This crate implements aselect, a safer alternative to the tokio select!-macro.
By using aselect, it becomes possible to avoid cancelling futures during normal
operations, eliminating a class of bugs. See the excellent RFD 400 from Oxide
for a great overview of cancellation safety in rust:
https://rfd.shared.oxide.computer/rfd/400 . aselect also avoids the “FutureLock”
class of bugs, described (also by Oxide) at https://rfd.shared.oxide.computer/rfd/0609,
because it doesn’t allow async code in handlers (only in the actual concurrent arms).
§Comparison with tokio::select
The regular select! macro from tokio is very useful, but it has two properties that can
be error-prone when said macro is used in a loop:
- As soon as one select arm completes, all other arms are canceled. Many futures are
not cancellation safe (e.g.
tokio::sync::mpsc::Sender::send). - When an arm has completed, while the handler is executing, other arms are no longer polled.
In contrast to select!, aselect has these differences:
- It implements
futures::Stream, meaning it can be polled multiple times. When polled repeatedly, it never implicitly cancels any futures; arms are polled until they become ready. It also implementscore::future::Future. - When polled, it always polls all active arms.
- It has a different syntax (that allows it to be formatted by rustfmt).
- It allows safe sharing of mutable state between select arms
§When to use tokio::select
Use tokio::select when:
- Cancellation of futures is a desired outcome
- Completion of one select arm means the continued execution of other arms is meaningless
§When to use aselect
Use aselect when:
- You’re processing multiple async sources of information in a loop
- When you need to run multiple futures to completion, while sharing state between them
§Tips
- Both the setup and handler blocks return
Option. This means the?operator can be used in them. - Use the
core::pin::pin!-macro to pin theaselect!expression when using it as afutures::Stream. - All handlers must return the same type. If no output is desired, they can just
return () (which is the default). Otherwise they must return
Option<Output<T>>. Note, since the handler is wrapped inSome, making sure the handler evaluates toOutputis enough. Or you can explicitlyreturn Some(Output::Value(x)).
§Implementation
aselect works by creating a set of structs that implement a state machine.
Each select arm is its own struct, and consists of two closures and a stored future.
One of the closures creates the future, and the other decides if the result of a future
should cause aselect itself to produce a value.
aselect does not allocate memory on the heap.
§Features
Enable the futures feature (enabled by default) for Stream support.
All aselect! invocations implement Stream (in addition to Future) when this
feature is enabled.
Modules§
- patterns
- Async application main loops
Macros§
- aselect
- Evaluate multiple different async operations concurrently.
Enums§
Traits§
- Safe
Result - Trait that must be implemented by the type returned by the handler expression of a
aselectarm. - Stream
- A stream of values produced asynchronously.