asynchronous rust future control-flow

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 una B

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