How do I synchronously return a value calculated in an asynchronous Future?
Asked Answered
C

3

74

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.

Centenarian answered 26/9, 2018 at 15:21 Comment(0)
N
116

Standard library futures

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);
}

Tokio

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.

async-std

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.

Futures 0.1

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.

Tokio

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:

Noumenon answered 26/9, 2018 at 15:41 Comment(7)
In futures 0.1, for simple cases, you only need to call wait: in my opinion is not very clear: it works only when a future resolve immediately. When you have something to be resolved in the future you need a runtime, also for simple cases.Octameter
@Shepmaster, as of Rust 1.45 is there a standard way (without using other crates) of doing this?Xanthophyll
@Xanthophyll no. See also How do I execute an async/await function without using any external dependencies?Noumenon
@Shepmaster, thanks. I see this as among the road blocks, hopeful we will have it some day?Xanthophyll
@Xanthophyll if you believe that adding one line to your Cargo.toml is a "road block", then Rust may not be a language you enjoy using. It's always possible that more things will be moved to the standard library, but there's not really a strong push to do so, especially when there are multiple ways to solve a problem with no clear benefit to one vs the other and it's easy to add dependencies.Noumenon
Also, PLEASE! don't overload the std lib. And no, the need to add a line to Cargo.toml ain't a reason to do that.Unhorse
Note that you need to add futures to your cargo.toml like this in order to get block_on: futures = { version="0", features=["executor"] }Millimicron
T
3

As this is the top result that come up in search engines by the query "How to call async from sync in Rust", I decided to share my solution here. I think it might be useful.

As @Shepmaster mentioned, back in version 0.1 futures crate had beautiful method .wait() that could be used to call an async function from a sync one. This must-have method, however, was removed from later versions of the crate.

Luckily, it's not that hard to re-implement it:

trait Block {
    fn wait(self) -> <Self as futures::Future>::Output
        where Self: Sized, Self: futures::Future
    {
        futures::executor::block_on(self)
    }
}

impl<F,T> Block for F
    where F: futures::Future<Output = T>
{}

After that, you can just do following:

async fn example() -> i32 {
    42
}

fn main() {
    let s = example().wait();
    println!("{:?}", s);
}

Beware that this comes with all the caveats of original .wait() explained in the @Shepmaster's answer.

Trave answered 21/10, 2021 at 19:18 Comment(0)
M
2

This works for me using tokio:

tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;
Millimicron answered 23/2, 2021 at 8:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.