How do I conditionally return different types of futures?

拟墨画扇 提交于 2019-11-26 05:56:40

问题


I have a method that, depending on a predicate, will return one future or another. In other words, an if-else expression that returns a future:

extern crate futures; // 0.1.23

use futures::{future, Future};

fn f() -> impl Future<Item = usize, Error = ()> {
    if 1 > 0 {
        future::ok(2).map(|x| x)
    } else {
        future::ok(10).and_then(|x| future::ok(x + 2))
    }
}

This doesn\'t compile:

error[E0308]: if and else have incompatible types
  --> src/lib.rs:6:5
   |
6  | /     if 1 > 0 {
7  | |         future::ok(2).map(|x| x)
8  | |     } else {
9  | |         future::ok(10).and_then(|x| future::ok(x + 2))
10 | |     }
   | |_____^ expected struct `futures::Map`, found struct `futures::AndThen`
   |
   = note: expected type `futures::Map<futures::FutureResult<{integer}, _>, [closure@src/lib.rs:7:27: 7:32]>`
              found type `futures::AndThen<futures::FutureResult<{integer}, _>, futures::FutureResult<{integer}, _>, [closure@src/lib.rs:9:33: 9:54]>`

The futures are created differently, and might hold closures, so their types are not equal. Ideally, the solution wouldn\'t use Boxes, since the rest of my async logic doesn\'t use them.

How is if-else logic in futures normally done?


回答1:


Either

Using futures::future::Either has no additional heap allocation:

extern crate futures; // 0.1.23

use futures::{
    future::{self, Either},
    Future,
};

fn f() -> impl Future<Item = usize, Error = ()> {
    if 1 > 0 {
        Either::A(future::ok(2).map(|x| x))
    } else {
        Either::B(future::ok(10).and_then(|x| future::ok(x + 2)))
    }
}

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

extern crate futures; // 0.1.23

use futures::{future, Future};

fn f() -> Box<Future<Item = usize, Error = ()>> {
    if 1 > 0 {
        Box::new(future::ok(2).map(|x| x))
    } else {
        Box::new(future::ok(10).and_then(|x| future::ok(x + 2)))
    }
}

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 Eithers if you have more than 2 conditions (Either<A, Either<B, C>>; Either<Either<A, B>, Either<C, D>>, etc.):

fn f(v: i32) -> impl Future<Item = i32, Error = ()> {
    use std::cmp::Ordering;
    match v.cmp(&0) {
        Ordering::Less => Either::A(future::ok(2).map(|x| -x)),
        Ordering::Equal => Either::B(Either::A(future::ok(0))),
        Ordering::Greater => Either::B(Either::B(future::ok(-2).map(|x| x * x))),
    }
}

See also:

  • Conditionally iterate over one of several possible iterators


来源:https://stackoverflow.com/questions/51885745/how-do-i-conditionally-return-different-types-of-futures

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!