Are async closures without an await statement more expensive than regular closures?

It seems that many of the adapters in futures::stream::StreamExt require an async closure regardless of whether or not there is anything to await on.

Is an async closure, without anything to await on, more expensive than a regular closure?

It depends. There might be an extra copy of some data that you could otherwise avoid, but they would be very close.

Note that tokio::stream::StreamExt have versions of several of the combinators without them being async.

In debug mode, definitely - there are more types. But in release mode, it's likely that it'll be all optimized out. You can also use std::future::ready which stabilizes in 8 days or futures::future::ready until then to shorten your code and avoid some difficult borrowing errors that occur with async blocks.

Regarding the non-async combinators, perhaps I am missing something here. The only two functions in StreamExt trait that I can see that take closures and that don't return futures are map and inspect. The others have some variant of the trait bound F: FnMut(..) -> Fut which (I think) means I have to wrap my non-async code in an async block.

For example, assume msg was a Result whose error variant can be safely ignored. I think (correct me if I am wrong) I have to write:

let messages = stream
    .filter_map(|msg| async {
        msg.ok()
    }).collect::<Vec<String>>().await;

Instead of something like:

let messages = stream
    .filter_map(Result::ok)
    .collect::<Vec<String>>().await;

I'm referring to Tokio's StreamExt. Tokio has its own StreamExt, distinct from futures' StreamExt.

1 Like