Search code examples
rustclosurestype-inference

Why does Rust infer FnMut instead of FnOnce for this closure, even though inferring FnMut causes an error?


Trying to compile this code:

fn main() {
    fn id(x: &mut u8) -> &mut u8 { x }
    let x = &mut 0_u8;
    let f = move || id(x);
}

results in an error:

error: captured variable cannot escape `FnMut` closure body
 --> main.rs:4:21
  |
4 |     let f = move || id(x);
  |                   - ^^^^^ 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

However, if I force the closure f to be inferred as FnOnce only (not FnMut), as follows:

fn main() {
    fn id(x: &mut u8) -> &mut u8 { x }
    let x = &mut 0_u8;
    fn force_fn_once<F: FnOnce() -> R, R>(f: F) -> F { f }
    let f = force_fn_once(move || id(x));
}

then the example compiles. I have tested this on Rust 1.65.0, as well as the latest nightly release at the time, 1.68.0-nightly (2022-12-14), and both with and without declaring the closure as move.

Why does the Rust compiler infer FnMut for f when doing so results in an error (and when it could simply infer FnOnce to avoid that error)? And, as a followup question, is there a better way to force the compiler to infer FnOnce?


Solution

  • I believe, without inference, the compiler attempts to deduce the closure's capabilities from the least-restrictive to most-restrictive (Fn -> FnMut -> FnOnce). It will see that Fn can't work because it would need to either move or mutably reborrow x, but it can type-check successfully with FnMut because it can mutably reborrow x to pass to id().

    However, the borrow checker only runs after type-deductions and its only then that the "escaped capture" emits an error. And the compiler doesn't backtrack to try again with a different function trait.

    Is there a better way to force the compiler to infer FnOnce?

    Something like let f: impl FnOnce() -> _ = ... might be available eventually, but until then, in this case you can introduce a statement to force a move of the captured variable:

    let f = move || { let x = x; id(x) };
                   // ^^^^^^^^^^
    

    EDIT: https://github.com/rust-lang/rust-clippy/issues/11562#issuecomment-1750237884 suggest a more concise form:

    let f = move || id({ x });