rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.96k stars 12.79k forks source link

Confusing error `captured variable cannot escape FnMut closure body` caused by unnecessary move closure #119733

Open tjallingt opened 10 months ago

tjallingt commented 10 months ago

Code

use futures::{stream, StreamExt};
use reqwest::Client;

async fn works(client: &Client, base_url: &str) {
    let _: Vec<_> = stream::iter(&[1])
        .map(|_| async {
            let url: String = format!("{base_url}/api/post");

            let _response = client.post(&url).send().await.unwrap();
        })
        .collect()
        .await;
}

async fn error(client: &Client, base_url: &str) {
    let _: Vec<_> = stream::iter(&[1])
        .map(move |_| async {
            let url: String = format!("{base_url}/api/post");

            let _response = client.post(&url).send().await.unwrap();
        })
        .collect()
        .await;
}

Current output

error: captured variable cannot escape `FnMut` closure body
   --> src\bin\backfill-tool.rs:175:27
    |
173 |       async fn error(client: &Client, base_url: &str) {
    |                                       -------- variable defined here
174 |           let _: Vec<_> = stream::iter(&[1])
175 |               .map(move |_| async {
    |  _________________________-_^
    | |                         |
    | |                         inferred to be a `FnMut` closure
176 | |                 let url: String = format!("{base_url}/api/post");
    | |                                             -------- variable captured here
177 | |
178 | |                 let _response = client.post(&url).send().await.unwrap();
179 | |             })
    | |_____________^ returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
    |
    = note: `FnMut` closures only have access to their captured variables while they are executing...
    = note: ...therefore, they cannot allow references to captured variables to escape

Desired output

No response

Rationale and extra context

While programming I copy pasted my own code containing a move closure. I then encountered this error and attempted to figure out what was going wrong in this code.

My attempts to fix the error involved changing the type of base_url, cloning base_url and moving around the creation of url. I did not consider that the move may have been the cause of this error, perhaps because my mental model of what move is doing exactly is not quite complete.

If one writes a closure without move and runs into issues where it is necessary, the compiler helpfully suggests adding it. In this case it would be nice if the compiler could check that the move is not necessary (and in fact is preventing the code from compiling) and then report a suggestion to remove it.

As it stands the error seems to suggest that the closure being FnMut is the cause of the error.

Other cases

No response

Rust Version

❯ rustc --version --verbose
rustc 1.75.0 (82e1608df 2023-12-21)
binary: rustc
commit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112
commit-date: 2023-12-21
host: x86_64-pc-windows-msvc
release: 1.75.0
LLVM version: 17.0.6

Anything else?

No response

Tharanishwaran commented 10 months ago
works uses a regular closure (|_| async { ... }) that borrows client and base_url by reference.
error uses a move closure (move |_| async { ... }) that takes ownership of these variables, leading to the error you encountered.
Tharanishwaran commented 10 months ago

*Remove move Keyword: In error, remove move to allow borrowing by reference instead of ownership transfer: Rust

async fn error(client: &Client, baseurl: &str) { // ... let : Vec<_> = stream::iter(&[1]) .map(|_| async { / use client and base_url by reference / }) .collect() .await; }

jieyouxu commented 9 months ago

@rustbot label +D-confusing +D-newcomer-roadblock -needs-triage