Using async
/await
Since Rust 1.39, you can use async
and await
syntax to cover most cases:
async fn a() -> usize {
2
}
async fn b() -> usize {
10
}
async fn f() -> usize {
if 1 > 0 {
a().await
} else {
b().await + 2
}
}
See also:
Either
Using futures::future::Either
via the FutureExt
trait has no additional heap allocation:
use futures::{Future, FutureExt}; // 0.3.5
async fn a() -> usize {
2
}
async fn b() -> usize {
10
}
fn f() -> impl Future<Output = usize> {
if 1 > 0 {
a().left_future()
} else {
b().right_future()
}
}
However, this requires a fixed stack allocation. If A
takes 1 byte and happens 99% of the time, but B
takes up 512 bytes, your Either
will always take up 512 bytes (plus some). This isn't always a win.
Boxed trait objects
Here we use FutureExt::boxed
to return a trait object:
use futures::{Future, FutureExt}; // 0.3.5
async fn a() -> usize {
2
}
async fn b() -> usize {
10
}
fn f() -> impl Future<Output = usize> {
if 1 > 0 {
a().boxed()
} else {
b().boxed()
}
}
As Matthieu M. points out, the two solutions can be combined:
I would note that there is a middle ground solution for the case of a large B
: Either(A, Box<B>)
. This way, you only pay for the heap allocation on the rare case where it's a B
Note that you can also stack Either
s if you have more than 2 conditions (Either<A, Either<B, C>>
; Either<Either<A, B>, Either<C, D>>
, etc.):
use futures::{Future, FutureExt}; // 0.3.5
async fn a() -> i32 {
2
}
async fn b() -> i32 {
0
}
async fn c() -> i32 {
-2
}
fn f(v: i32) -> impl Future<Output = i32> {
use std::cmp::Ordering;
match v.cmp(&0) {
Ordering::Less => a().left_future(),
Ordering::Equal => b().left_future().right_future(),
Ordering::Greater => c().right_future().right_future(),
}
}
See also:
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…