0

The Iterator trait's method any takes a parameter implementing FnMut trait.

Here's the definiton

#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
fn any<F>(&mut self, f: F) -> bool
where
    Self: Sized,
    F: FnMut(Self::Item) -> bool,
{
    #[inline]
    fn check<T>(mut f: impl FnMut(T) -> bool) -> impl FnMut((), T) -> ControlFlow<()> {
        move |(), x| {
            if f(x) { ControlFlow::BREAK } else { ControlFlow::CONTINUE }
        }
    }
    self.try_fold((), check(f)) == ControlFlow::BREAK
}

I think the rule of FnMut is: The closure captures variables by mutable reference.


But the following code goes wrong because of "use of moved value: rust".

This means the variable rust has been moved into the closure, which has conflicts with the rule of FnMut trait.

// Situation 1
let rust = String::from("rust");
let list: Vec<String> = vec![String::from("rust")];
list.into_iter().any(move |s| s == rust);
dbg!(rust); // error here

However, the following code is also refused by the compiler due to the rule of FnMut trait.

// Situation 2
let rust = String::from("rust");
let list: Vec<String> = vec![String::from("rust")];
list.into_iter().any(move |s| {
    let temp = rust; // error here
    s == temp
});

So my question is, why there are different behaviors about the FnMut trait between these two situations?

I think the second situation is easy to understand. But why in the first situation, the captured variable will be consumed?

  • Does this answer your question? [When does a closure implement Fn, FnMut and FnOnce?](https://stackoverflow.com/questions/30177395/when-does-a-closure-implement-fn-fnmut-and-fnonce) – Chayim Friedman Jun 13 '22 at 15:04
  • @ChayimFriedman I don't think that is a duplicate of this, though it overlaps considerably. – Peter Hall Jun 13 '22 at 16:51
  • @PeterHall The OP misunderstood what `FnMut` is, so I think it's an appropriate duplicate. Of course you're free to think otherwise. – Chayim Friedman Jun 13 '22 at 23:30

2 Answers2

2

The variable has been moved into the closure because you used the move keyword. It has nothing to do with it being a FnMut.

The second code block produces an error because the closure might be called multiple times (since it's constrained to be FnMut), but the line let temp = rust; moves that variable again, into a variable that is dropped within the function body. This means that the environment of the closure would be invalid if it was ever called again. All functions and closures implement FnOnce, but this one can only implement FnOnce - i.e. it cannot implement Fn or FnMut.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Do you mean "the capture‘s behavior" and "the use of captured variables" are not the same thing, and the "Fn*" trait is associated with the latter one? – Autumnal_Joy Jun 13 '22 at 15:08
  • Yes. Closures usually capture by reference. The `move` keyword changes that behaviour to make them capture by value. This is orthogonal to the behaviour implied by the specific `Fn*` traits. – Peter Hall Jun 13 '22 at 15:16
  • Thanks! I used to think the rule of FnMut is: The closure captures variables by mutable reference, but I was wrong. It should be: The closure uses variables by mutable reference. – Autumnal_Joy Jun 13 '22 at 15:38
  • Yes that's right. In a `move FnMut` it could also still mutate the variables in its environment - the difference is that it owns them. – Peter Hall Jun 13 '22 at 16:33
1

It's not FnMut, it's move. The move keyword in move |s| causes captured variables to be moved into the closure. Leave it off to use the default capture mode, by reference:

let rust = String::from("rust");
let list: Vec<String> = vec![String::from("rust")];
list.into_iter().any(|s| s == rust);
dbg!(rust);

Playground

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • I know. But the `any` takes FnMut. I think it's not allowed to pass FnOnce as the parameter. – Autumnal_Joy Jun 13 '22 at 14:58
  • @Autumnal_Joy That's sort of right. All closures are `FnOnce`, but some are _also_ `FnMut`, and some of those are also `Fn`. The closure you provided in the second example is _only_ `FnOnce`, so cannot be used as an argument that requires an `FnMut`. – Peter Hall Jun 13 '22 at 15:09
  • Thanks a lot! I now understand why I am wrong. I mistakenly thought of “move” and “FnOnce” as the same thing. – Autumnal_Joy Jun 13 '22 at 15:14