Search code examples
rustasync-awaitrust-tokioreqwest

How do I solve "cannot return value referencing local data" when using threads and async/await?


I am learning Rust especially multithreading and async requests in parallel.

I read the documentation and still I do not understand where I made a mistake. I assume I know where, but do not see how to resolve it.

main.rs

use std::thread;

struct Request {
    url: String,
}

impl Request {
    fn new(name: &str) -> Request {
        Request {
            url: name.to_string(),
        }
    }

    async fn call(&self, x: &str) -> Result<(), Box<dyn std::error::Error>> {
        let resp = reqwest::get(x).await;
        Ok(())
    }
}

#[tokio::main]
async fn main() {
    let requests = vec![
        Request::new("https://www.google.com/"),
        Request::new("https://www.google.com/"),
    ];
    let handles: Vec<_> = requests
        .into_iter()
        .map(|request| {
            thread::spawn(move || async {
                request.call(&request.url).await;
            })
        })
        .collect();

    for y in handles {
        println!("{:?}", y);
    }
}
error[E0515]: cannot return value referencing local data `request`
  --> src/main.rs:29:35
   |
29 |               thread::spawn(move || async {
   |  ___________________________________^
30 | |                 request.call(&request.url).await;
   | |                 ------- `request` is borrowed here
31 | |             })
   | |_____________^ returns a value referencing data owned by the current function

Cargo.toml

[dependencies]
reqwest = "0.10.4"
tokio = { version = "0.2", features = ["full"] }

Solution

  • Like closures, async blocks capture their variables as weakly as possible. In order of preference:

    1. immutable reference
    2. mutable reference
    3. by value

    This is determined by how the variable is used in the closure / async block. In your example, request is only used by reference, so it is only captured by reference:

    async {
        request.call(&request.url).await;
    }
    

    However, you need to transfer ownership of the variable to the async block so that the variable is still alive when the future is eventually executed. Like closures, this is done via the move keyword:

    thread::spawn(move || async move {
        request.call(&request.url).await;
    })
    

    See also:


    It is very unlikely that you want to mix threads and async at this point in your understanding. One is inherently blocking and the other expects code to not block. You should follow the example outlined in How can I perform parallel asynchronous HTTP GET requests with reqwest? instead.

    See also: