Как я могу условно вернуть разные типы фьючерсов?

У меня есть метод, который, в зависимости от предиката, вернется в будущем. Другими словами, выражение if-else, возвращающее будущее:

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))
    }
}

Это не компилируется:

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}, _>, [[email protected]/lib.rs:7:27: 7:32]>'
              found type 'futures::AndThen<futures::FutureResult<{integer}, _>, futures::FutureResult<{integer}, _>, [[email protected]/lib.rs:9:33: 9:54]>'

Фьючерсы создаются по-разному и могут содержать замыкания, поэтому их типы не равны. В идеале, решение не будет использовать Box es, так как остальная часть моей асинхронной логики не использует их.

Как нормальная логика if-else обычно выполняется?

Ответ 1

Either

Использование futures::future::Either не требует дополнительного выделения кучи:

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)))
    }
}

Однако для этого требуется фиксированное выделение стека. Если A занимает 1 байт и происходит в 99% случаев, но B занимает 512 байт, ваш Either всегда будет занимать 512 байт (плюс некоторые). Это не всегда победа.

Объекты в штучной упаковке

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)))
    }
}

Как отмечает Матье М., два решения можно объединить:

Я хотел бы отметить, что для случая большого B: Either(A, Box<B>) существует среднее решение. Таким образом, вы платите только за выделение кучи в тех редких случаях, когда это B

Обратите внимание, что вы также можете сложить Either, если у вас более двух условий (Either<A, Either<B, C>>; Either<Either<A, B>, Either<C, D>> и т.д.):

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))),
    }
}

Смотрите также: