Why the closure is not moved?

In the following code , x is not moved as expected

fn outer() {
  fn inner(y : impl FnOnce()) {
    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.


In outer, it knows that the closure is Copy, so it can be passed by value into inner and still be called afterwards.

Here I made the closure non-Copy by capturing a Vec by value

fn outer() {
  fn inner(_ : impl FnOnce()) {}
  let non_copy = vec![3];
  let x = move||{

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.

Your code compiles with either of these changes:

-  fn inner(y : impl FnOnce()) {
+  fn inner(y : impl FnOnce() + Copy) {
-  fn inner(y : impl FnOnce()) {
-    y();
+  fn inner(y : impl FnOnce() + Clone) {
+    (y.clone())();
1 Like

got it. the key is in my case , x implements the Copy trait automatically , inner just moved the copied one. thank you guys.

Can I interpret as : inner will work only on the static type of y and will not lookup Copy trait in the dynamic type ?

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.

thank you. I use static and dynamic roughly here.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.