Reputation: 35186
Here's an example of using Tokio to run a function that returns a future:
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(())
}));
}
However, when the function in question (i.e. deferred_task
) is non-trivial, the code becomes much more complex when I write it, because the ?
operation doesn't seem to easily mix with returning a future:
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
is a Result
, it's meaningless to wrap it in result, and it breaks the impl Future
return type.
Instead you get a deeply nested chain of:
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 })),
}
}
The more results you have, the deeper the nesting; exactly what the ?
operator solves normally.
Am I missing something? Is there some syntax sugar to make this easier?
Upvotes: 1
Views: 439
Reputation: 431479
I do not see how async
/ await
syntax will categorically help you with Either
. Ultimately, you still need to return a single concrete type, and that's what Either
provides. async
/ await
will reduce the need for combinators like Future::map
or Future::and_then
however.
See also:
That being said, you don't need to use Either
here.
You have consecutive Result
-returning functions, so you can borrow a trick from JavaScript and use an IIFE to use use the ?
operator. Then, we can "lift up" the combined Result
into a future and chain it with the future from the receiver:
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))
}
In the future, that IIFE could be replaced with a try
block, as I understand it.
You could also go the other way and convert everything to a future:
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))
}
This would be helped with async
/ await
syntax:
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)
}
I have also seen improved syntax for constructing the Either
by adding left
and right
methods to the Future
trait:
foo.left();
// vs
Either::left(foo);
However, this doesn't appear in any of the current implementations.
A
Future
is aResult
No, it is not.
There are two relevant Future
s to talk about:
Notably, Future::poll
returns a type that can be in two states:
In the futures crate, "success" and "failure" are tied to "complete", whereas in the standard library they are not. In the crate, Result
implements IntoFuture
, and in the standard library you can use future::ready
. Both of these allow converting a Result
into a future, but that doesn't mean that Result
is a future, no more than saying that a Vec<u8>
is an iterator, even though it can be converted into one.
It's possible that the ?
operator (powered by the Try
trait), will be enhanced to automatically convert from a Result
to a specific type of Future
, or that Result
will even implement Future
directly, but I have not heard of any such plans.
Upvotes: 1
Reputation: 72044
Is there some syntax sugar to make this easier?
Yes, it's called async/await, but it's not quite ready for wide consumption. It is only supported on nightly, it uses a slightly different version of futures that Tokio only supports via an interop library that causes additional syntactic overhead, and documentation for the whole thing is still spotty.
Here are some relevant links:
What is the purpose of async/await in Rust?
https://jsdw.me/posts/rust-asyncawait-preview/
https://areweasyncyet.rs/
Upvotes: 0