In the following code , x is not moved as expected
fn outer() {
fn inner(y : impl FnOnce()) {
y();
y(); // A , can't call again , compiler say y is moved
}
let x = ||{};
inner(x); // in my understanding , x is moved to inner , because of A
x(); // but this call is valid , why?
}
Well, in inner, all you know about the closure is that you can call it once. Since you don't know whether calling it more than once is valid, the compiler prevents you from doing that. However in outer, we do know exactly what the closure is, and in outer we can tell that, actually, this closure is ok to call multiple times.
fn outer() {
fn inner(_ : impl FnOnce()) {}
let non_copy = vec![3];
let x = move||{
&non_copy;
};
inner(x);
x();
}
and it errors as expected
error[E0382]: borrow of moved value: `x`
--> src/lib.rs:8:3
|
4 | let x = move||{
| - move occurs because `x` has type `[closure@src/lib.rs:4:11: 6:4]`, which does not implement the `Copy` trait
...
7 | inner(x);
| - value moved here
8 | x();
| ^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` due to previous error
Inside of inner, the only thing the code can rely on is that y: impl FnOnce. That means you can only call it once. Why? Well, closures are like ad-hoc structs, and FnOnce consumes that struct... something like this:
let x = AdHocClosure(|| Your Code);
impl FnOnce<()> for AdHocClosure {
type Output = ();
extern "rust-call" fn call_once(self, args: ()) -> Self::Output {
/* Runs Your Code */
}
}
Note that calling the closure consumes self (the closure). That's why it moved.
OK, so why does it work when you just define the closure, pass it somewhere, and then call it again?
Closures implement Clone and Copy if the variables they capture allow it. Your closure doesn't actually capture anything at all, so it implements Copy (and Clone). inner can't take advantage of this, because the trait bounds said it didn't care -- you can't take advantage of Copy if it's not part of your trait bounds.
But when your closure creation is still in scope, there is no trait bound in affect -- that code can see that your closure is Copy. So, it created a copy.
I don't think static and dynamic are the right words here. It's more that the compiler only looks at one function at the time, so it doesn't know what the closure is going to be when it compiles inner. In fact, the compiler will verify that inner is correct no matter which closure you use, as long as the closure is FnOnce. Since you could call inner with a closure where two calls would be invalid, you can't call it twice.