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
?
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 });