Why closure writing to its context still implements traits FnOnce?

I am confused. I have watched nearly all videos and official documentation on closures as well as a couple of unofficial, but still have difficulty predicting the behavior of the compiler.

Especially useful I've found this explanation and related article.

Particularly this:

  let mut b = "def".to_string();
  let closure_move_1 = move | mut a : String |
  {
     b.push( 'g' );
     a.push_str( &b );
     println!( "closureMove : {:?}", a );
     a
  };

  // try to uncomment
  // fn_once_1( closure_move_1 );
  // works
  // ? but why? routine write to its closure!

  // try to uncomment
  // fn_1( closure_move_1 );
  // ! this closure implements `FnMut`, not `Fn`
  // ? but why fn_once_1 works?

  // try to uncomment
  // fn_mut_1( closure_move_1 );
  // works as predicted

Here is a playground with source code.
What did I miss?

Could you elaborate on why you think it shouldn't work based on your understanding? It may help to improve documentations. To answer your question closures impl FnOnce if can be called once by consuming itself but you may already have seen this kind of explanations.

1 Like

The code you've posted here doesn't quite match the playground link you provided:

  let b = "def".to_string();
  let closure1 = | a |
  {
     println!( "closure : {:?}", a );
     // but returning a compiles for every type of closure
     // a
     b
  };

Here, b is moved into the closure when it's created, but it is then moved out again when you return it. This means the closure cannot be called again: It no longer owns a String that can be returned from the second call.

One way to make this compile is to return a clone of b instead of moving it out of the closure on return:

  let b = "def".to_string();
  let closure1 = | a |
  {
     println!( "closure : {:?}", a );
     b.clone()
  };

The three closure traits form a hierarchy, and anything that implements one trait also implements the previous ones:

  • FnOnce represents a closure that can be called at least once, and is implemented for all closures. If this is the only thing you know about a closure, you can only call it once because it might become invalid during the call (as above).
  • FnMut represents a closure that can be called multiple times, but could be non-reentrant. If this is the only thing you know about it, you can't pass it as a callback to itself or call it from multiple threads simultaneously.
  • Fn represents a closure that can be called multiple times, and is also safe to be run concurrently with itself, either via a callback or in multiple threads*.

* If it can be transferred to another thread, which is governed by Send / Sync.

2 Likes

People get confused by "at least once" vs "at most once", because both are true, depending on your perspective.

  • Code of an FnOnce closure can rely on it being called 0 or 1 times, so it can do things like moves and drops (it can be called at most once, so you won't observe any invalid state on a second call).

  • Code that uses FnOnce trait promises to call it either 0 or 1 times only. It doesn't matter if it could have called a closure more times, because it won't. This behavior is compatible with Fn and FnMut, because they're not bothered by being called only 0 or 1 times (they're types that can be called at least once in a context that uses them at most once).

6 Likes

Indeed. The "(can be called) at least once" is still the most technically correct phrasing, though; in the same fashion that a Clone type can, at the very least, be .clone()d. Could it be Copy-ed? Maybe. But if we don't know more, the most one can suppose, is that minimal bound. So, indeed, from a user's perspective, trait bounds and their minimal constraints become maximal :upside_down_face:


So, back at the closure_move_1 example, which features a closure which:

  • is not Fn :x:, i.e., callable (any number of times) through shared reference(s) (&) to the closure('s environment) —and thus, concurrently— since it accesses &mut b when called (to mutate b), so it needs that unique &mut access to be called.

  • is FnMut :white_check_mark:, i.e., callable (any number of times) through a unique reference (&mut) to the closure('s environment) —and thus, necessarily sequentially— since &mut access to its capture (b: String), suffices to perform the b.push(...); ... logic).

  • is FnOnce :white_check_mark:, i.e., callable once / through owned / by-value access to the closure('s environment), as a corollary of the previous bullet ("once" is a valid choice of "any number of times").

The key thing to understand here is that all:

  • All FnMut closures are thus / also / a fortiori FnOnce as well.

    • (once matches the any number of times sequentially capability)

    And, similarly,

  • All Fn closures are thus / also / a fortiori FnMut as well.

    • (any number of times sequentially matches the any number of times concurrently capability)

That is:

FnFnMutFnOnce

  • From: image

  • In the same fashion as:

    • CopyClone; or

    • OrdPartialOrd; or

    • ExactSizeIteratorIterator.

3 Likes

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.