0

I'm searching a way to pass a variable url_path to this thread, as in this code:

async fn download_dist(all_paths: Vec<String>, mode: &str) {
    let mut download_path = "";
    let mut root_path = "";
    let mut url_path = String::new();

    if mode.contains("pool") {
        root_path = POOL_ROOT_PATH;
        url_path = format!("http://{}/{}", DEBIAN_REPOSITORY, DEBIAN_PATH);
        download_path = DEBIAN_POOL_PATH;
    } else {
        root_path = DIST_ROOT_PATH;
        url_path = format!("http://{}/{}/", DEBIAN_REPOSITORY, DEBIAN_DIST_PATH);
        download_path = DEBIAN_DIST_PATH;
    }

    let responses = futures::stream::iter(all_paths.into_iter().map(move |path| {
        tokio::spawn(async move {
            println!("{}", url_path);
        })
    }))
    .buffer_unordered(10)
    .collect::<Vec<_>>();
    responses.await;
}

Error is : cannot move out of url_path, a captured variable in an FnMut closure move out of url_path occurs here

3
  • don't declare it as mut or better create a copy in side of the async block Commented Dec 18, 2022 at 15:51
  • Once you're done mutating it, let url_path = url_path. Commented Dec 18, 2022 at 16:05
  • ``` let url_path = url_path; let responses = futures::stream::iter( all_paths.into_iter().map(move | path | tokio::spawn( async move { let url_path = url_path; ``` Same error Commented Dec 18, 2022 at 16:07

2 Answers 2

3

.map calls the closure you provide many times.

The problem is that your url_path variable can only be moved to a thread once. So in order to move it to many threads (as you do), you have to clone it:

async fn download_dist(all_paths: Vec<String>, mode: &str) {
    let mut download_path = "";
    let mut root_path = "";
    let mut url_path = String::new();

    if mode.contains("pool") {
        root_path = POOL_ROOT_PATH;
        url_path = format!("http://{}/{}", DEBIAN_REPOSITORY, DEBIAN_PATH);
        download_path = DEBIAN_POOL_PATH;
    } else {
        root_path = DIST_ROOT_PATH;
        url_path = format!("http://{}/{}/", DEBIAN_REPOSITORY, DEBIAN_DIST_PATH);
        download_path = DEBIAN_DIST_PATH;
    }

    let responses = futures::stream::iter(all_paths.into_iter().map(move |path| {
        let url_path = url_path.clone();
        tokio::spawn(async move {
            println!("{}", url_path);
        })
    }))
    .buffer_unordered(10)
    .collect::<Vec<_>>();
    responses.await;
}

Side note:

If you set a variable exactly once before using it and the compiler understands that, you can skip the initialization and the mut, like so:

async fn download_dist(all_paths: Vec<String>, mode: &str) {
    let download_path;
    let root_path;
    let url_path;

    if mode.contains("pool") {
        root_path = POOL_ROOT_PATH;
        url_path = format!("http://{}/{}", DEBIAN_REPOSITORY, DEBIAN_PATH);
        download_path = DEBIAN_POOL_PATH;
    } else {
        root_path = DIST_ROOT_PATH;
        url_path = format!("http://{}/{}/", DEBIAN_REPOSITORY, DEBIAN_DIST_PATH);
        download_path = DEBIAN_DIST_PATH;
    }

    let responses = futures::stream::iter(all_paths.into_iter().map(move |path| {
        let url_path = url_path.clone();
        tokio::spawn(async move {
            println!("{}", url_path);
        })
    }))
    .buffer_unordered(10)
    .collect::<Vec<_>>();
    responses.await;
}
Sign up to request clarification or add additional context in comments.

Comments

0

You're using mut but you don't need to. Consider:

let (download_path, url_path, root_path) = if mode.contains("pool")
{
    (
        POOL_ROOT_PATH,
        format!("http://{}/{}",DEBIAN_REPOSITORY,DEBIAN_PATH),
        DEBIAN_POOL_PATH
    )
}
else
{
    (
        POOL_ROOT_PATH,
        format!("http://{}/{}/",DEBIAN_REPOSITORY,DEBIAN_DIST_PATH),
        DEBIAN_POOL_PATH
    )
};

Collapsing it like this exposes that you're not really changing either download_path or url_path anyway.

Tip: In Rust when you have let mut x = "" and then later assign to it, that's a sign you might want let x = if (...) { } instead.

If you're still stuck on the references, you can go down the Arc path:

  let url_path = Arc::new(url_path);

  let responses = futures::stream::iter(all_paths.into_iter().map(|path| {
        let url_path = url_path.clone();

        tokio::spawn(async move {
            println!("{}", url_path);
        })
    }))

This way each spawned task gets their "own" copy of the url_path, but to avoid needless copies, they're all Arc references to the same thing.

3 Comments

code is way better thanks :) But error is same :/ Cannot move out of url_path, a captured variable in an FnMut closure move out of url_path occurs here
You can clone() it before each call, or use the Arc approach shown in my edit.
If you use Arc, I recommend Arc::clone(&url_path) instead of url_path.clone(), as .clone() is ambiguous (could also be String::clone)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.