asynchronous - ¿Cómo devuelvo condicionalmente diferentes tipos de futuros?
rust future (1)
Tengo un método que, dependiendo de un predicado, devolverá uno u otro futuro. En otras palabras, una expresión if-else que devuelve un futuro:
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))
}
}
Esto no compila:
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]>`
Los futuros se crean de manera diferente y pueden tener cierres, por lo que sus tipos no son iguales.
Idealmente, la solución no usaría
Box
es, ya que el resto de mi lógica asíncrona no los usa.
¿Cómo se hace normalmente la lógica if-else en los futuros?
Either
Usar
futures::future::Either
no tiene una asignación de almacenamiento dinámico adicional:
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)))
}
}
Sin embargo, esto requiere una asignación de pila fija.
Si
A
toma 1 byte y ocurre el 99% del tiempo, pero
B
toma 512 bytes, su
Either
siempre
ocupará 512 bytes (más algunos).
Esto no siempre es una victoria.
Objetos de rasgo en caja
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)))
}
}
Como señala Matthieu M. , las dos soluciones se pueden combinar:
Me gustaría señalar que hay una solución intermedia para el caso de una gran
B
:Either(A, Box<B>)
. De esta manera, solo pagará por la asignación de almacenamiento dinámico en el caso poco frecuente de que sea unaB
Tenga en cuenta que también puede apilar
Either
s si tiene más de 2 condiciones (
Either<A, Either<B, C>>
;
Either<Either<A, B>, Either<C, D>>
, etc.):
Ver también:
- Condicionalmente iterar sobre uno de varios posibles iteradores