Is tokio's `select!` cancel safe?

When using a future with select! in a loop and to avoid loosing data the futures must be "cancel safe".

My question is: is the following future cancel safe?

async fn foo() {
    select! {
        _ = goo() => { /* ... */ }
        _ = hoo() => { /* ... */ }
    }
}

So if I have a loop with a select! that use foo

loop {
    select! {
        _ = sleep(Duration::from_secs(1)) => { /* ... */ }
        _ = foo() => { /* ... */ }
    }
}

Does any data loss may happen?

The docs for select! is quite large, so maybe a miss the information but is seems there is no information about nested select! or its the cancel safety-ness.

My guess would be that the following also applies to the select! inside of foo:

To determine whether your own methods are cancellation safe, look for the location of uses of .await. This is because when an asynchronous method is cancelled, that always happens at an .await. If your function behaves correctly even if it is restarted while waiting at an .await, then it is cancellation safe.

(From the cancellation safety docs you linked above.)

So if goo, hoo (both being implicitly awaited by select!) and both of their handlers are cancellation safe, foo is as well.

It's impossible to say whether these async function calls are compatible with select!, because that is an invisible implementation detail that could be broken with a simple .await somewhere.

IMHO select! is a fundamentally flawed construct that wants a guarantee from futures that their API just doesn't provide. It's a footgun.

If you want a timeout, use tokio::time::timeout or timeout_at.

If you want all of the results at once, use join! or join_all.

If you want to get any of the results as soon as they arrive, use a channel: send results wrapped in an enum to the channel, read in one place and match. You can put both futures that send to the channel and a third that reads from it in join! to have all three polled without using spawn