I am playing with examples from Understanding Closures in Rust.. I defined the following structure:
struct MyStruct {
text: &'static str,
number: u32,
}
impl MyStruct {
fn new(text: &'static str, number: u32) -> MyStruct {
MyStruct {
text: text,
number: number,
}
}
// We have to specify that 'self' is an argument.
fn get_number(&self) -> u32 {
self.number
}
// We can specify different kinds of ownership and mutability of self.
fn inc_number(&mut self) {
self.number += 1;
}
// There are three different types of 'self'
fn destructor(self) {
println!("Destructing {}", self.text);
}
}
I created these "testing" functions to classify a closure based on compile time errors:
fn is_fn<A, R>(_x: fn(A) -> R) {}
fn is_Fn<A, R, F: Fn(A) -> R>(_x: &F) {}
fn is_FnMut<A, R, F: FnMut(A) -> R>(_x: &F) {}
fn is_FnOnce<A, R, F: FnOnce(A) -> R>(_x: &F) {}
The article demonstrates following example as a FnOnce
closure:
let obj1 = MyStruct::new("Hello", 15);
let obj2 = MyStruct::new("More Text", 10);
// obj1 is owned by the closure
let mut closure4 = move |x: &MyStruct| {
obj1.destructor();
x.get_number()
};
is_FnMut(&closure4);
This has a compilation error:
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> src/main.rs:36:24
|
36 | let mut closure4 = move |x: &MyStruct| {
| ^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
37 | obj1.destructor();
| ---- closure is `FnOnce` because it moves the variable `obj1` out of its environment
...
40 | is_FnMut(&closure4);
| -------- the requirement to implement `FnMut` derives from here
That makes sense. From my understanding, the compiler infers that the value is moved into the closure because the destructor is called.
I was trying to see what else causes the closure to be inferred as FnOnce
. I declared the closure as a move
closure:
let obj1 = MyStruct::new("Hello", 15);
let obj2 = MyStruct::new("More Text", 10);
// obj1 is owned by the closure
let mut closure4 = move |x: &MyStruct| {
obj1.get_number();
x.get_number()
};
is_Fn(&closure4);
is_FnMut(&closure4);
To my surprise, this actually passes, so this closure is actually considered to be also Fn
, not exclusively FnOnce
as I expected. Why is that? I thought if a value is moved into closure, it wouldn't implement Fn
but only FnOnce
.
So I though, "Okay, maybe somehow obj1
is not really moved into the closure, let me test that". So I tried to use obj1
in any way after constructing closure4
, the compiler indicates that obj1
was indeed moved into the closure:
let obj1 = MyStruct::new("Hello", 15);
let obj2 = MyStruct::new("More Text", 10);
// obj1 is owned by the closure
let mut closure4 = move |x: &MyStruct| {
obj1.get_number();
x.get_number()
};
obj1.get_number();
error[E0382]: borrow of moved value: `obj1`
--> src/main.rs:40:5
|
33 | let obj1 = MyStruct::new("Hello", 15);
| ---- move occurs because `obj1` has type `MyStruct`, which does not implement the `Copy` trait
...
36 | let mut closure4 = move |x: &MyStruct| {
| ------------------- value moved into closure here
37 | obj1.get_number();
| ---- variable moved due to use in closure
...
40 | obj1.get_number();
| ^^^^ value borrowed here after move
Can someone enlighten this behaviour for me? What must hold true for a closure to implement none of fn
, Fn
, FnMut
, but only implement FnOnce
?