aselect 0.4.0

Opinionated replacement for tokio::select!, avoiding certain pitfalls.
Documentation
[![Build](https://github.com/avl/aselect/actions/workflows/rust.yml/badge.svg)](https://github.com/avl/aselect/actions/workflows/rust.yml)

# ASelect

This is a rust crate intended to provide an opinionated solution to two potential pitfalls 
when the tokio `select!`-macro is used in loops. While said macro is useful, it has two 
error-prone characteristics, when used in loops:

 * Each invocation of `select!` will cancel all but one future.
 * Handlers with async blocks may starve all select arms (when one `select!` arm
   becomes ready, the others are not polled until its handler completes).

This crate solves this by:

 * Never cancelling futures (except when explicitly asked to)
 * Allowing futures to live for multiple iterations of select loops
 * Not allowing async code in handler blocks (only in arms).

Additionally, aselect:

 * Can be formatted by rustfmt.
 * Does not allocate memory
 * Can be used in a `no_std` context.
 * Allows sharing mutable state between select arms
 * Implements [Stream]https://docs.rs/futures/latest/futures/prelude/trait.Stream.html.

## Motivation

See [motivating example](MOTIVATION.md) for a detailed example showcasing the problems aselect is meant to solve.

## Example

```rust
#[tokio::main]
async fn main() {
    let counter = 0u32;
    let mut stream = pin!(aselect!(
        {
            mutable(counter);
        },
        timer(
            {
                tokio::time::sleep(std::time::Duration::from_millis(1))
            },
            async |fut1| {
                fut1.await;
            },
            |result| {
                *counter += 1;
                None
            }
        ),
        output(
            {
                tokio::time::sleep(std::time::Duration::from_millis(3))
            },
            async |fut2| {
                fut2.await;
            },
            |result| {
                Some(*counter)
            }
        ),
    ));
    while let Some(item) = stream.next().await {
        println!("Value: {}", item);
    }
}

```

See docs and `examples/` for more complex examples. Especially, the above simple
example does not show more advanced state tracking (for example, `constant` and `borrowed` captures).
This example also doesn't show canceling.

# Motivation for this crate

Being able to easily cancel futures is a useful feature of Rust. As is the ability to have
explicit control over the execution of async programs, using Rust's extendable and programmable
async features.

However, there are a few patterns that have turned out to be error-prone:
  1. Having futures that exist, but are not polled
  2. Canceling futures

The async programming model allows applications to be written mostly as if they were
normal sequential programs. However, this abstraction can be quite leaky. Point 1 above
means that a program can deadlock, if a future that owns a lock is not polled. Point 2 means
that async methods may not behave as expected.

A good introduction to the first problem can be found here:
<https://rfd.shared.oxide.computer/rfd/0609> (TLDR, if future that is not being polled
owns a resource, that resource will never become available).

An illustration of the other problem is something like this:

```rust
    async fn receive_command(&mut self) -> (Command, UserInfo) {
        let cmd = self.command_link.receive().await;
        self.security_log.send(cmd).await;
        let user_info = self.db.lookup_user_info(cmd.user).await;
        (cmd, user_info)
    }
```

This method receives a command, logs it in a security log, then looks up some additional
information and returns this to the caller. Consider what happens if this `receive_command` 
method is used as a future in regular tokio `select!`. Things may appear to work, but if
another future becomes ready after a command has been received and logged, but before 
`lookup_user_info` completes, the command will be silently dropped. In this case,
the command will still appear in the `security_log`, it will just not have any other effect.

Troubleshooting this kind of problem can be frustrating. Basically, any application that
uses tokio `select!` needs to annotate all its methods with a `# Cancel safety` header
in its documentation, and all code must be inspected to see if cancel safety 
invariants are honored or not. Once again, Oxide has an excellent guide on the subject:
https://rfd.shared.oxide.computer/rfd/400 .

Note that this analysis must be done globally. It is typically hard to make all code
`Cancel safe`. Thus, one must ensure that async code in one module isn't called from a 
sometimes-canceled future elsewhere in the application.

Surely there is a better way!

This crate suggests that futures should never be canceled, except potentially during
shutdown or as a response to faults. `aselect!` is a macro that allows writing
select loops that never cancel futures, and which always poll all live futures. 
This means that the pitfalls mentioned in the beginning of this README are avoided.