Closures don't move?

Not quite sure why this is valid… doesn’t cb get moved in the first call?

fn main()
{
    let cb = |s:&str| println!("result: {}", s);
    
    do_something(cb, "hello");
    
    //cb didn't move, this is still valid!
    do_something(cb, "world");
}

fn do_something<F>(f:F, label:&str) 
where F: (Fn(&str) -> ())
{
    f(label);
}

Interestingly, adding references everywhere also works:

fn main()
{
    let cb = |s:&str| println!("result: {}", s);
    
    do_something(&cb, "hello");
    do_something(&cb, "world");
}

fn do_something<F>(f:&F, label:&str) 
where F: (Fn(&str) -> ())
{
    f(label);
}

Or even adding references at the call site, but not in the receiver:

fn main()
{
    let cb = |s:&str| println!("result: {}", s);
    
    do_something(&cb, "hello");
    do_something(&cb, "world");
}

fn do_something<F>(f:F, label:&str) 
where F: (Fn(&str) -> ())
{
    f(label);
}

Can someone please help me understand why this is all valid and/or what the differences are when adding references here?

Two relevant things here:

  1. a closure “has a #[derive(Copy)] on it”, meaning that if the environment it captures is Copy, it becomes Copy too.

    fn assert_val_is_copy (_: impl Copy) {}
    
    fn main ()
    {
        let cb = |s: &str| println!("result: {}", s); // Captures {s} - {s} = nothing => Copy
        assert_val_is_copy(cb);
    }
    

    whereas

    fn assert_val_is_copy (_: impl Copy) {}
    
    fn main ()
    {
        struct NotCopy;
        let captured = NotCopy;
        let cb = move |s: &str| { // captures {s, captured} - {s} = {captured}
            let _ = captured;
            println!("result: {}", s);
        };
        assert_val_is_copy(cb); // ERROR
    }
    

    leading to your code with my capture

    fn main()
    {
        struct NotCopy;
        let captured = NotCopy;
        let cb = move |s: &str| { // captures {s, captured} - {s} = {captured}
            let _ = captured;
            println!("result: {}", s);
        };
        do_something(cb, "hello");
        do_something(cb, "world"); // ERROR
    }
    
    fn do_something<F> (f: F, label: &'_ str) 
    where
        F : Fn(&str) -> (),
    {
        f(label);
    }
    

    yielding the following error:

    error[E0382]: use of moved value: `cb`
      --> src/main.rs:12:18
       |
    7  |     let cb = move |s: &str| { // captures {s, captured} - {s} = {captured}
       |         -- move occurs because `cb` has type `[closure@src/main.rs:7:14: 10:6 captured:main::NotCopy]`, which does not implement the `Copy` trait
    ...
    11 |     do_something(cb, "hello");
       |                  -- value moved here
    12 |     do_something(cb, "world"); // ERROR
       |                  ^^ value used here after move
    
  2. the other thing is that, even if something is moved into / captured by a closure, a shared reference to the closure may suffice to call it: that’s what being Fn(...) -> _ means.

    This is best seen with a counter example: what does a closure that can’t be called from a shared reference look like? There are two possibilities here:

    • FnMut
      the closure needs at least a unique reference to it to be callable. This is often the case when the closure body uses a unique reference (&mut _) to mutate something:

      fn main()
      {
          let mut cb_counter = 0;
          let cb = |s: &str| { // captures {s, &mut cb_counter} - {s} = {&mut cb_counter}
              cb_counter += 1; // *(&mut cb_counter) += 1;
              println!("result: {}", s);
          };
          do_something(cb, "hello"); // error, cb needs at least a unique reference to be callable
      }
      
      fn do_something<F> (f: F, label: &'_ str) 
      where
          F : Fn(&str) -> (), // needs to be callable from a shared reference
      {
          f(label); // (&f)(label)
      }
      
      • errors with:

        error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`
         --> src/main.rs:4:14
          |
        4 |     let cb = |s: &str| { // captures {s, &mut cb_counter} - {s} = {&mut cb_counter}
          |              ^^^^^^^^^ this closure implements `FnMut`, not `Fn`
        5 |         cb_counter += 1; // *(&mut cb_counter) += 1;
          |         ---------- closure is `FnMut` because it mutates the variable `cb_counter` here
        ...
        8 |     do_something(cb, "hello"); // error, cb needs at least a unique reference to be callable
          |     ------------ the requirement to implement `Fn` derives from here
        

      In this case the compilation error comes from the bounds on do_something being too strict (we are not calling f from multiple places at the same time, thus we don’t need the : Fn bound). Changing the F : Fn(&str) -> () bound to F : FnMut(&str) -> () (and the f: F argument into mut f: F, to be able to use &mut f) fixes this:

      fn do_something<F> (mut f: F, label: &'_ str) 
      where
          F : FnMut(&str) -> (),
      {
          f(label); // (&mut f)(label)
      }
      
    • FnOnce
      the closure needs to consume part of its environment when called; this can happen when the closure returns an owned value (that is not Copy) from its captured environment:

      fn main()
      {
          let mut string = String::from("Hello");
          // captures {s, string} - {s} = {string}
          let append_and_return = |s: &str| -> String {
              use ::std::fmt::Write;
              write!(&mut string, ", {}!", s).unwrap();
              string
          };
          do_something(append_and_return, "world"); // error
      }
      
      fn do_something<F> (mut f: F, label: &'_ str) 
      where
          F : FnMut(&str) -> String,
      {
          let string = f(label);
          dbg!(string);
      }
      
      • errors with:

        error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
          --> src/main.rs:5:29
           |
        5  |     let append_and_return = |s: &str| -> String {
           |                             ^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
        ...
        8  |         string
           |         ------ closure is `FnOnce` because it moves the variable `string` out of its environment
        9  |     };
        10 |     do_something(append_and_return, "world"); // error
           |     ------------ the requirement to implement `FnMut` derives from here
        
        

      Again, changing the strict bounds (we are not calling f multiple times, just once) to looser ones (mut f: F no longer needed by the way) fixes this:

      fn do_something<F> (f: F, label: &'_ str) 
      where
          F : FnOnce(&str) -> String,
      {
          let string = f(label);
          dbg!(string);
      }
      

    Hence the following trait bounds hierarchy:

    Fn ⇒ FnMut ⇒ FnOnce

    • which reads as: a Fn closure (i.e., a closure flexible enough to be callable from multiple places at once) is also a FnMut closure (i.e., a closure that can be called multiple times sequentially), and a FnMut closure is also a FnOnce closure (i.e., a closure that can be called once).

    That’s why, the bound on a closure, when it is one of the arguments of a function, should be as loose as possible:

    • FnOnce when called one or zero times;

    • FnMut otherwise.

    • requiring Fn is very rare (and thus usually wrong), since such bound is usually only needed when there may be multiple concurrent (or parallel with an added + Sync bound) calls to the closure, like the ::rayon crate does.

7 Likes

In case you haven’t seen it yet, I made a blog post about how closures are desugared that should give some insight into this.

3 Likes

I did see it but at the time I was even greener than now :wink: Will take another look…

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.