Async Error Handling
Async error handling uses the exact same Result<T, E> and ? operator idioms as synchronous Rust: an async fn that can fail returns Result<T, E> (so its desugared return type is impl Future<Output = Result<T, E>>), and chaining .await? first awaits the future to obtain the Result and then propagates any Err(e) via ?.
Chaining .await? Across Asynchronous Steps
use std::future::ready;
use std::pin::pin;
use std::task::{Context, Poll, Waker};
#[derive(Debug)]
enum OrderError {
InvalidQuantity,
OutOfStock { item_id: u32 },
}
async fn validate_quantity(qty: u32) -> Result<u32, OrderError> {
ready(if qty > 0 {
Ok(qty)
} else {
Err(OrderError::InvalidQuantity)
})
.await
}
async fn reserve_inventory(item_id: u32, qty: u32) -> Result<String, OrderError> {
ready(if qty <= 10 {
Ok(format!("reserved {qty}x item #{item_id}"))
} else {
Err(OrderError::OutOfStock { item_id })
})
.await
}
async fn place_order(item_id: u32, qty: u32) -> Result<String, OrderError> {
// `.await` resolves the Future to `Result<...>`, and `?` unwraps `Ok` or returns `Err`:
let valid_qty = validate_quantity(qty).await?;
let confirmation = reserve_inventory(item_id, valid_qty).await?;
Ok(confirmation)
}
fn eval_ready<F: std::future::Future>(fut: F) -> F::Output {
let mut pinned = pin!(fut);
let mut cx = Context::from_waker(Waker::noop());
match pinned.as_mut().poll(&mut cx) {
Poll::Ready(out) => out,
Poll::Pending => panic!("unexpected Pending"),
}
}
fn main() {
println!("order 1: {:?}", eval_ready(place_order(101, 3)));
println!("order 2: {:?}", eval_ready(place_order(101, 0)));
println!("order 3: {:?}", eval_ready(place_order(101, 50)));
}
order 1: Ok("reserved 3x item #101")
order 2: Err(InvalidQuantity)
order 3: Err(OutOfStock { item_id: 101 })
Type Inference in async { ... } Blocks Using ?
When using the ? operator inside a standalone async { ... } block (rather than an async fn with an explicit return signature), the compiler knows the Ok(val) type from the final expression, but may not know which concrete error type E ? should convert into via From::from (E0282: type annotations needed).
Ending the async block with Ok::<_, MyError>(value) (using a turbofish on Ok) specifies the exact error type for the entire async block in one place:
use std::future::ready;
use std::pin::pin;
use std::task::{Context, Poll, Waker};
fn main() {
let task = async {
let raw: &str = ready(Ok::<&str, String>("42")).await?;
let parsed: u32 = raw.parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
// Turbofish `Ok::<u32, String>(...)` anchors both `T` and `E` for the `async` block!
Ok::<u32, String>(parsed * 2)
};
let mut pinned = pin!(task);
let mut cx = Context::from_waker(Waker::noop());
println!("task output: {:?}", pinned.as_mut().poll(&mut cx));
}
task output: Ready(Ok(84))
Choosing Box<dyn Error + Send + Sync> for Multi-Threaded Async Tasks
In synchronous single-threaded code, Box<dyn std::error::Error> is often used as a type-erased error. In multi-threaded async code where futures must implement Send (F: Future + Send), holding a plain Box<dyn Error> across an .await point (or returning it from a tokio::spawn task) fails to compile because Box<dyn Error> is !Send! Always use Box<dyn std::error::Error + Send + Sync + 'static> for type-erased errors in async Rust.