Using variable inside futures::ForEach results into: cannot return value referencing local variable

Hello,

This is the first time I am trying to use async with Rust, however I get some weird error:

error[E0515]: cannot return value referencing local variable `repo`
  --> src/main.rs:35:34
   |
35 |           .for_each(|(repo, resp)| async {
   |  __________________________________^
36 | |             pb.inc(1);
37 | |             match resp {
38 | |                 Ok(r) => match r.status() {
39 | |                     StatusCode::OK => println!("{}", repo),
   | |                                                      ---- `repo` is borrowed here
...  |
44 | |             }
45 | |         })
   | |_________^ returns a value referencing data owned by the current function

Ideally, I'd like to collect all those repo strings into the one Vec and print it at the end of the program instead of doing println! inside the async code. But before I was able to try constructing a vector, I got stuck with aforementioned error.

My code is here:

use futures::{stream, StreamExt};
use indicatif::ProgressBar;
use reqwest::{Client, StatusCode};
use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let owner_alias = client
        .get("https://src.fedoraproject.org/extras/pagure_owner_alias.json")
        .send()
        .await?
        .json::<HashMap<String, HashMap<String, Vec<String>>>>()
        .await?;

    let keys = owner_alias["rpms"].keys();
    let pb = ProgressBar::new(keys.len() as u64);
    let dead_status = stream::iter(keys)
        .map(|repo| {
            let client = &client;
            let url = format!(
                "https://src.fedoraproject.org/rpms/{}/raw/master/f/dead.package",
                repo
            );
            async move {
                let resp = client.head(&url).send().await;
                (repo, resp)
            }
        })
        .buffer_unordered(16); // 16 requests in parallel

    dead_status
        .for_each(|(repo, resp)| async {
            pb.inc(1);
            match resp {
                Ok(r) => match r.status() {
                    StatusCode::OK => println!("{}", repo),
                    StatusCode::NOT_FOUND => (),
                    _ => eprintln!("Unknown response: {:?}", r.status()),
                },
                Err(e) => eprintln!("Connection Error: {:?}", e),
            }
        })
        .await;

    pb.finish_with_message("done");

    Ok(())
}

If you have any other suggestion, please let me know. This is essentially my first "real" code in Rust, so I might be doing things in some weird / wrong way.

Thanks in advance!

You pretty much never want for_each. Prefer a loop instead.

while let Some((repo, resp)) = dead_status.next().await {
    ... 
} 
2 Likes

Thank you! That made it work and code to look much more clear. Just one more question: how would one run .collect() on a such loop?

Thanks again!

You can either use combinators and then use collect() like normal, or you can repeatedly call push on a vector.

You pretty much never want for_each.

This is not accurate :frowning:, at least if you try to extend it to iterators.

for_each is often critical for performance. For example, a for loop on Chain, assuming it doesn't contain a break/return/etc., is simply incorrect code; it should be a for_each call.

1 Like

This advice is specific to the Stream trait. You can't override the asynchronous for_each to gain advantages from internal iteration, and Iterator::for_each does not have the lifetime issues that the for_each on Stream does.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.