Interestingly, the error message becomes better the more clear you make your demand for the closure to be FnMut
.
If it’s just calling multiple times, you get
error[E0382]: use of moved value: `closure`
--> src/main.rs:19:5
|
18 | closure();
| --------- `closure` moved due to this call
19 | closure();
| ^^^^^^^ value used here after move
|
note: closure cannot be invoked more than once because it moves the variable `x` out of its environment
--> src/main.rs:10:26
|
10 | let y = &mut x;
| ^
note: this value implements `FnOnce`, which causes it to be moved when called
--> src/main.rs:18:5
|
18 | closure();
| ^^^^^^^
which points to the point where x
is determined to be moved, but is still suboptimal. The help message
note: closure cannot be invoked more than once because it moves the variable `x` out of its environment
--> src/main.rs:10:26
|
10 | let y = &mut x;
| ^
doesn’t make all that clear that we are still talking about closure
here, not nested_closure
, and it also doesn’t point out the move
of nested_closure
that contributes to this mention of x
resulting in a move.
If you instead require FnMut
explicitly such as in
struct Foo {
x: i32,
}
fn fn_mut(_: impl FnMut()) {}
fn main() {
let mut x = Foo { x: 1 };
let mut closure = move || {
let mut nested_closure = move || {
let y = &mut x;
y.x += 1;
};
nested_closure();
nested_closure(); // This works fine
};
fn_mut(closure);
}
it’s a bit better:
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> src/main.rs:10:23
|
10 | let mut closure = move || {
| ^^^^^^^ this closure implements `FnOnce`, not `FnMut`
11 | let mut nested_closure = move || {
12 | let y = &mut x;
| - closure is `FnOnce` because it moves the variable `x` out of its environment
...
20 | fn_mut(closure);
| ------ ------- the requirement to implement `FnMut` derives from here
| |
| required by a bound introduced by this call
|
note: required by a bound in `fn_mut`
--> src/main.rs:5:19
|
5 | fn fn_mut(_: impl FnMut()) {}
| ^^^^^^^ required by this bound in `fn_mut`
now the help
makes clear that it talks about the outer closure in the message where the occurrence of x
is pointed to. Still not optimal though. The message is even better if the compiler no longer needs to infer FnMut
based on the closure but is told explicitly by having the closure expression appear in a place that directly expects FnMut
:
struct Foo {
x: i32,
}
fn fn_mut(_: impl FnMut()) {}
fn main() {
let mut x = Foo { x: 1 };
fn_mut(move || {
let mut nested_closure = move || {
let y = &mut x;
y.x += 1;
};
nested_closure();
nested_closure(); // This works fine
});
}
which gives the error
error[E0507]: cannot move out of `x`, a captured variable in an `FnMut` closure
--> src/main.rs:11:34
|
8 | let mut x = Foo { x: 1 };
| ----- captured outer variable
9 |
10 | fn_mut(move || {
| ------- captured by this `FnMut` closure
11 | let mut nested_closure = move || {
| ^^^^^^^ `x` is moved here
12 | let y = &mut x;
| -
| |
| variable moved due to use in closure
| move occurs because `x` has type `Foo`, which does not implement the `Copy` trait
only now does the error message point at all the relevant parts. The closure we are talking about, then the move
statement on the inner closure (which is now considered the position where x
is moved), and the place where x
is mentioned (which is now only presented as the reason the inner closure expression moves x
).
It’d assume based on this experience, that there could still reasonably be room for improvement of the error messages in the other two cases.
Note that these differences are not unreasonable, the versions of code really are increasingly explict, in that
- the first version (unlike the other too ones) leaves it to the compiler to infer that having
closure
implement FnMut
would be a desirable idea; but this is more of a side-thought limited to a note
that wants to be concise on the whole “what if closure
was supposed to be FnMut
(or Fn
), and why isn’t it, anyways” tangential
- the second version is a bit more subtly still more implicit than the third; the approach here still is to leave it to the compiler to infer the kind of closure, and only demand the right kind of trait later. This is subtle, because this rule in Rust that closure traits are only not inferred in cases where the closure expression is directly passed to a closure-expecting function is a bit of a niche rule. None of this should matter that much for the error message though; I think here’s the most straightforward potential for improvement.