I am trying to use hyper to grab the content of an HTML page and would like to synchronously return the output of a future. I realized I could have picked a better example since synchronous HTTP requests already exist, but I am more interested in understanding whether we could return a value from an async calculation.
extern crate futures; extern crate hyper; extern crate hyper_tls; extern crate tokio; use futures::{future, Future, Stream}; use hyper::Client; use hyper::Uri; use hyper_tls::HttpsConnector; use std::str; fn scrap() -> Result<String, String> { let scraped_content = future::lazy(|| { let https = HttpsConnector::new(4).unwrap(); let client = Client::builder().build::<_, hyper::Body>(https); client .get("https://hyper.rs".parse::<Uri>().unwrap()) .and_then(|res| { res.into_body().concat2().and_then(|body| { let s_body: String = str::from_utf8(&body).unwrap().to_string(); futures::future::ok(s_body) }) }).map_err(|err| format!("Error scraping web page: {:?}", &err)) }); scraped_content.wait() } fn read() { let scraped_content = future::lazy(|| { let https = HttpsConnector::new(4).unwrap(); let client = Client::builder().build::<_, hyper::Body>(https); client .get("https://hyper.rs".parse::<Uri>().unwrap()) .and_then(|res| { res.into_body().concat2().and_then(|body| { let s_body: String = str::from_utf8(&body).unwrap().to_string(); println!("Reading body: {}", s_body); Ok(()) }) }).map_err(|err| { println!("Error reading webpage: {:?}", &err); }) }); tokio::run(scraped_content); } fn main() { read(); let content = scrap(); println!("Content = {:?}", &content); }
The example compiles and the call to read()
succeeds, but the call to scrap()
panics with the following error message:
Content = Err("Error scraping web page: Error { kind: Execute, cause: None }")
I understand that I failed to launch the task properly before calling .wait()
on the future but I couldn't find how to properly do it, assuming it's even possible.
The async/await syntax is supported directly by the Rust compiler. Many utility types, macros and functions are provided by the futures crate. They can be used in any async Rust application. Execution of async code, IO and task spawning are provided by "async runtimes", such as Tokio and async-std.
async transforms a block of code into a state machine that implements a trait called Future . Whereas calling a blocking function in a synchronous method would block the whole thread, blocked Future s will yield control of the thread, allowing other Future s to run. The value returned by async fn is a Future .
Futures in Rust are analogous to promises in JavaScript. They are a powerful abstraction over the concurrency primitives available in Rust. They are also a stepping stone to async/await, which allows users to write asynchronous code that looks like synchronous code.
Let's use this as our minimal, reproducible example:
async fn example() -> i32 { 42 }
Call executor::block_on
:
use futures::executor; // 0.3.1 fn main() { let v = executor::block_on(example()); println!("{}", v); }
Use the tokio::main
attribute on any function (not just main
!) to convert it from an asynchronous function to a synchronous one:
use tokio; // 0.3.5 #[tokio::main] async fn main() { let v = example().await; println!("{}", v); }
tokio::main
is a macro that transforms this
#[tokio::main] async fn main() {}
Into this:
fn main() { tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap() .block_on(async { {} }) }
This uses Runtime::block_on
under the hood, so you can also write this as:
use tokio::runtime::Runtime; // 0.3.5 fn main() { let v = Runtime::new().unwrap().block_on(example()); println!("{}", v); }
For tests, you can use tokio::test
.
Use the async_std::main
attribute on the main
function to convert it from an asynchronous function to a synchronous one:
use async_std; // 1.6.5, features = ["attributes"] #[async_std::main] async fn main() { let v = example().await; println!("{}", v); }
For tests, you can use async_std::test
.
Let's use this as our minimal, reproducible example:
use futures::{future, Future}; // 0.1.27 fn example() -> impl Future<Item = i32, Error = ()> { future::ok(42) }
For simple cases, you only need to call wait
:
fn main() { let s = example().wait(); println!("{:?}", s); }
However, this comes with a pretty severe warning:
This method is not appropriate to call on event loops or similar I/O situations because it will prevent the event loop from making progress (this blocks the thread). This method should only be called when it's guaranteed that the blocking work associated with this future will be completed by another thread.
If you are using Tokio 0.1, you should use Tokio's Runtime::block_on
:
use tokio; // 0.1.21 fn main() { let mut runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime"); let s = runtime.block_on(example()); println!("{:?}", s); }
If you peek in the implementation of block_on
, it actually sends the future's result down a channel and then calls wait
on that channel! This is fine because Tokio guarantees to run the future to completion.
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With