Существует ли более эргономичный синтаксис для Either при использовании фьючерсов? - PullRequest
0 голосов
/ 14 марта 2019

Вот пример использования Tokio для запуска функции, которая возвращает будущее:

use futures::sync::oneshot;
use futures::Future;
use std::thread;
use std::time::Duration;
use tokio;

#[derive(Debug)]
struct MyError {
    error_code: i32,
}

impl From<oneshot::Canceled> for MyError {
    fn from(_: oneshot::Canceled) -> MyError {
        MyError { error_code: 1 }
    }
}

fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
    let (sx, rx) = oneshot::channel();
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(100));
        sx.send(100).unwrap();
    });
    return rx.map_err(|e| MyError::from(e));
}

fn main() {
    tokio::run(deferred_task().then(|r| {
        println!("{:?}", r);
        Ok(())
    }));
}

Однако, когда рассматриваемая функция (т.е. deferred_task) нетривиальна, код становится гораздо болеесложный, когда я пишу это, потому что операция ? не может легко смешаться с возвращением будущего:

fn send_promise_to_worker(sx: oneshot::Sender<i32>) -> Result<(), ()> {
    // Send the oneshot somewhere in a way that might fail, eg. over a channel
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(100));
        sx.send(100).unwrap();
    });
    Ok(())
}

fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
    let (sx, rx) = oneshot::channel();
    send_promise_to_worker(sx)?; // <-------- Can't do this, because the return is not a result
    return rx.map_err(|e| MyError::from(e));
}

A Future равно a Result, этобессмысленно переносить его в результате, и он ломает тип возврата impl Future.

Вместо этого вы получаете глубоко вложенную цепочку:

fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
    let (sx, rx) = oneshot::channel();
    match query_data() {
        Ok(_i) => match send_promise_to_worker(sx) {
            Ok(_) => Either::A(rx.map_err(|e| MyError::from(e))),
            Err(_e) => Either::B(futures::failed(MyError { error_code: 2 })),
        },
        Err(_) => Either::B(futures::failed(MyError { error_code: 2 })),
    }
}

полный код

Чем больше результатов, тем глубже вложение;именно то, что обычно решает оператор ?.

Я что-то упустил?Есть ли синтаксический сахар, чтобы сделать это проще?

Ответы [ 2 ]

1 голос
/ 14 марта 2019

Я не понимаю, как синтаксис async / await категорически поможет вам с Either.В конечном итоге вам все еще нужно возвращать один конкретный тип, и это то, что обеспечивает Either.async / await уменьшит потребность в комбинаторах, таких как Future::map или Future::and_then однако.

См. Также:


При этом вам не нужно использовать Either здесь.

У вас есть последовательные Result -возврат функций, так что вы можете позаимствовать уловку из JavaScript и использовать IIFE , чтобы использовать оператор ?.Затем мы можем «поднять» объединенный Result в будущее и связать его с будущим от получателя:

fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
    let (tx, rx) = oneshot::channel();

    let x = (|| {
        let _i = query_data().map_err(|_| MyError { error_code: 1 })?;
        send_promise_to_worker(tx).map_err(|_| MyError { error_code: 2 })?;
        Ok(())
    })();

    future::result(x).and_then(|()| rx.map_err(MyError::from))
}

В будущем этот IIFE может быть заменен блоком try, насколько я понимаю.

Вы также можете пойти другим путем и преобразовать все в будущее:

fn deferred_task() -> impl Future<Item = i32, Error = MyError> {
    let (tx, rx) = oneshot::channel();

    query_data()
        .map_err(|_| MyError { error_code: 1 })
        .into_future()
        .and_then(|_i| {
            send_promise_to_worker(tx)
                .map_err(|_| MyError { error_code: 2 })
                .into_future()
        })
        .and_then(|_| rx.map_err(MyError::from))
}

Этот поможет с async /Синтаксис await:

async fn deferred_task() -> Result<i32, MyError> {
    let (tx, rx) = oneshot::channel();

    query_data().map_err(|_| MyError { error_code: 1 })?;

    send_promise_to_worker(tx).map_err(|_| MyError { error_code: 2 })?;

    let v = await! { rx }?;

    Ok(v)
}

Я также видел улучшенный синтаксис для построения Either путем добавления left и right методов к признаку Future:

foo.left();
// vs
Either::left(foo);

Однако это не отображается ни в одной из текущих реализаций.

A Future равно a Result

Нет, это не.

Есть два соответствующих Future s, о которых можно поговорить:

В частности, Future::poll возвращает тип, который может находиться в двух состояниях:

  • Complete
  • Not complete

В ящике с фьючерсами «успех» и «провал» привязаны к «завершению», тогда как в стандартной библиотеке - нет.В ящике Result реализуется IntoFuture, а в стандартной библиотеке вы можете использовать future::ready.Оба из них позволяют конвертировать Result в будущее, но это не значит, что Result - это будущее, не более, чем утверждение, что Vec<u8> - итератор, даже если он можетможно преобразовать в единицу.

Возможно, что оператор ? (питается от черты Try) будет расширен для автоматического преобразования из Result в определенный тип Future, иличто Result даже реализует Future напрямую, но я не слышал ни о каких таких планах.

0 голосов
/ 14 марта 2019

Есть ли синтаксический сахар, чтобы сделать это проще?

Да, он называется async / await, но он не совсем готов для широкого потребления.Он поддерживается только ночью, он использует немного другую версию фьючерсов, которую Tokio поддерживает только через библиотеку взаимодействия, которая вызывает дополнительные синтаксические издержки, и документация для всего этого по-прежнему пятнистая.

Вот некоторые соответствующие ссылки:

Какова цель асинхронизации / ожидания в Rust?
https://jsdw.me/posts/rust-asyncawait-preview/
https://areweasyncyet.rs/

...