rust-lang / rust-lang/rust

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

Open
#140,875 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-diagnostics T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

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:
https://github.com/rust-lang/rust/issues/99279

It's most similar to the diagnostic suggested here:
https://github.com/rust-lang/rust/issues/44611#issuecomment-332052977

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.
https://github.com/thomaseizinger/rust-futures-bounded/pull/11/commits/008af99cf8b6a99d436f87f9183eae71af2db25f

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Rust playground reproducer linked in the issue and compare the current FnMut diagnostic with the desired mutable-borrow wording. The issue names no repository file or test; done means this example reports the repeated mutable borrow in a more helpful way.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.