Skip to content

Misleading "captured variable cannot escape FnMut closure body" diagnostic, should be "can't mutable borrow more than once" #140875

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
teor2345 opened this issue May 9, 2025 · 0 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@teor2345
Copy link
Contributor

teor2345 commented May 9, 2025

Code

use std::collections::HashMap;

pub fn mut_refs_pair<'a>(
    nums: &'a mut HashMap<u32, u8>,
    strs: &'a mut HashMap<u32, String>,
) -> impl Iterator<Item = (&'a mut u8, Option<&'a mut String>)> {
    // TODO: work out how to return &mut String here, without a
    // "captured variable cannot escape `FnMut` closure body" compiler error
    nums.iter_mut().map(|(id, num)| (num, strs.get_mut(id)))
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=8f47967d049435bc0f84cb62a057a3eb

Current output

error: captured variable cannot escape `FnMut` closure body
  |
5 |     strs: &'a mut HashMap<u32, String>,
  |     ---- variable defined here
...
9 |     nums.iter_mut().map(|(id, num)| (num, strs.get_mut(id)))
  |                                   - ^^^^^^----^^^^^^^^^^^^^
  |                                   | |     |
  |                                   | |     variable captured here
  |                                   | returns a reference to a captured variable which escapes the closure body
  |                                   inferred to be a `FnMut` closure
  |
  = 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
  = note: requirement occurs because of a mutable reference to `u8`
  = note: mutable references are invariant over their type parameter
  = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

Desired output

cannot borrow `strs` as mutable more than once at a time

The issue with this code is that the compiler can't statically guarantee that each id accesses a unique HashMap item. We know this to be true due to the definition of HashMap, but that's hard to prove with generic key and hasher types.

The standard "cannot borrow strs as mutable more than once at a time" error would be much more helpful here. An error about accessing a collection item as mutable more than once at a time would be even more useful.

Ideally, the compiler could encourage the user towards a working implementation like this:

use std::collections::HashMap;

pub fn mut_refs_pair<'a>(
    nums: &'a mut HashMap<u32, u8>,
    strs: &'a mut HashMap<u32, String>,
) -> impl Iterator<Item = (&'a mut u8, Option<&'a mut String>)> {
    // We need to convince the borrow checker that we are only returning one mutable reference
    // to each strs item. We can do this efficiently by creating a hashmap of mutable
    // references, then removing each item from the hashmap as we iterate over the nums.
    let mut mut_refs_strs = strs
        .iter_mut()
        .map(|(id, st)| (*id, st))
        .collect::<HashMap<u32, &mut String>>();
    nums.iter_mut().map(move |(id, num)| {
        let st = mut_refs_strs.remove(id);
        (num, st)
    })
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=6242cd4dbc29ca60773cf4c674ece882

Rationale and extra context

The type of the closure is not explicit in this code, and the user doesn't have much influence over the inferred type. Even if the inferred type is changed, it doesn't really help the user understand why the problem is happening.

Other cases

This is similar to other issues about FnMut closures, but the solution and desired diagnostic output is different:
#99279

It's most similar to the diagnostic suggested here:
#44611 (comment)

Most online answers suggest using Mutex or RefCell, which is completely unnecessary in this situation, and can lead to poorer performance, or runtime panics (if implemented incorrectly):
https://www.reddit.com/r/rust/comments/qotdo1/comment/hjphkp7/
https://stackoverflow.com/a/62563511

The last one is actually closest, suggesting fold.

Rust Version

1.86.0

(whatever environment is on play.rust-lang.org, but this isn't specific to the OS or commit)

Anything else?

This came up in real-world code when trying to create a mutable iterator across futures with associated data.

The implementation ensures each future has exactly one associated data item, so it is sound to return (&mut Fut, &mut Data). And the user can't get at the futures or data without this method.
thomaseizinger/rust-futures-bounded@008af99

@teor2345 teor2345 added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

1 participant