Fn FnMut and FnOnce

I'm reading: Closures: Anonymous Functions that Capture Their Environment - The Rust Programming Language
And I believe that I found contradicting sentences, that is:

" FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment . To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once."

And couple of sentences later:

All closures implement FnOnce because they can all be called at least once.

From what I understand the second sentence is incorrect.
Anyone?

1 Like
  • FnMut = 1 or more
  • Fn = 1 or more
  • FnOnce = 1

All of them can be called at least once (i.e. if you call them once, all of them work).

1 Like

Yes, but the first bolded by me sentence says that FnOnce can be called only once. This is different to at least once.
So if the second provided by me in OP sentence was correct and if all closures were implementing FnOnce, they could be called only once according to the first bolded sentence.

No, the text is correct. You're interpreting "at least once" as if it meant "FnOnce can be called once or more", but the subject here is the set of closures, and "at least once" means "if you need only one call, then all of them support one call or (some of them) support more calls".

I actually disagree and I believe that you're incorrect. FnOnce means what? At least once or only once?

FnOnce is for 0 or 1 calls only. It's directly tied to semantics of fn call(self) which consumes self.

FnMut is fn call(&mut self), which can be called multiple times (0, 1, or more).

That means if you need to call a function 0 or 1 times, then both FnMut and FnOnce satisfy that requirement.

Exactly, so according to:

Is this sentence correct or not? Even according to your last post?

Yes, that's absolutely correct. When number of calls you need does not exceed 1, then all of the closures satisfy that requirement.

"at least once" here does not mean "once or more", it means "if you need to call a closure exactly 1 times, then all closures types compatible, because number of times they can be called is either exactly (1), or (1 or more)"

Doesn't FnOnce implies that the closure can be called only once?

Yes, it does. And it is valid to call FnMut once and never call it again. It is valid to call Fn once and never call it again. This makes them compatible with FnOnce interface.

FnMut can also be called many times, which is why FnOnce doesn't implement FnMut. But FnMut implements FnOnce because both are fine being called exactly 1 time.

But there is fundamental difference in being able to call something once (or more) or being able to call something just once. Which one FnOnce implies?

FnOnce interface promises that you can call something 0 or 1 times.

FnMut function can be called 0, 1 or 2 or more times, therefore it's compatible with being called 0 or 1 times, therefore it's compatible with FnOnce.

So the second sentence breaks the promise because it is possible to call it more than once and the promise was zero or one.

That's not how it works. The FnOnce interface says what you are allowed to do with the function, not what the function may be capable of in general.

If you say you will call something once, then you can use any of the closures. The "unused" calls don't matter here, because they satisfy your requirement to be called once.

The first quoted snippet confuses how a closure captures its environment with which Fn... traits it implements. The Fn... traits are implemented automatically based on how the values are used when the closure is called, whereas the capture modes are determined by how the closure is defined. Thus,

is not true. Even if a closure implements only FnOnce, it may still capture several variables from its environment in different ways. It's move closures that always consume (or copy). But move closures, just like non-move closures, can still implement any of the valid combinations of Fn, FnMut and FnOnce, because it's how the variables are used when the closure is called that matters for that purpose.

One factor that confuses things is that the Fn... traits a closure implements automatically are determined by the compiler based on the usage of the captured variables inside the closure, which is the same thing it uses to determine how each variable is captured. But there is a difference. Capture modes in a non-move closure are chosen with the granularity of a single variable: thus a closure may capture a by reference, b by mutable reference, and c by value. But which Fn... traits it implements are chosen for the whole closure: in this scenario, probably only FnOnce, but it depends on whether c is of a Copy type or not.

The way the documentation reads makes it sound like FnOnce closures always consume, FnMut closures always borrow mutably and Fn closures always share, which is wrong and the source of your confusion. In reality, all three kinds of closures may capture parts of their environment in any of those three ways.

3 Likes

I agree with OP that this section in the book

  • FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment . To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.
  • FnMut can change the environment because it mutably borrows values.
  • Fn borrows values from the environment immutably.

is phrased suboptimally. I wouldn’t necessarily call it wrong, but it can at least be misunderstood and is IMO imprecise. (Edit: ... when taken literally it kinda is wrong but this paragraph is about giving the overall idea behind these traits in the form of a first introduction so giving all the details immediately would also be questionable.)

When dealing with traits, there are always two sides to look at it from. One side is from the implementor's point of view (in this case the closure), and the other is from the user's side of view (in this case the caller). This is because a trait is a promise you make about something being possible.

  1. As the closure, implementing FnOnce is making a promise that you can be called once.
  2. As the caller using an FnOnce, you have been promised that you can call it once.

So in the first case, implementing the trait is an at least once. In the latter case, using the closure is an at most once. The at most once may feel a bit forced, but when "at most once" is used, it is about what you can do through the FnOnce trait. Even if a closure could be called multiple times through FnMut, you cannot do so through the FnOnce trait. This is important because the traits typically come up as generic parameters, and in that case you typically only know that it is FnOnce.

4 Likes

I would still disambiguate this into

As the caller using an FnOnce , you have been promised that you can call it at least once.

Nontheless, when I have been promised that what I get can be called at least once, then the generic code I write (being the caller) can still only do at most one call, since it has to work with all possible closure types that implement FnOnce, including the ones that can only be called exactly once.

IMO this table is misleading. I don’t see any way in which “1 or more” makes much sense here. I would change the table to:

  • FnMut = any number of calls (in sequence)
  • Fn = any number of calls (in sequence or concurrently)
  • FnOnce = 0 or 1 call

And now we’re describing what a caller can do with an argument f: T when only constraining T with T: FnMut(...), T: Fn(...), or T: FnOnce(...), respectively.


Or we could have this table

  • FnMut = any number of calls (in sequence)
  • Fn = any number of calls (concurrently)
  • FnOnce = at least 1 call

Describing what the minimal requirement on a closure type is in order for the implementor to be able to create an impl FnMut<...>, impl Fn<...>, or impl FnOnce<...>, respectively, for the closure type.

Fn = any number of calls (in sequence or parallel)

*in sequence or concurrently; Sync would be needed for parallelism :wink:


To summarize: a Trait / an interface is a minimal prototype / contract.

If something implements Jump, then that thing may also implement Run. But from the point of view of a user of the trait, or a caller, as @alice has put it, if all you know is that something can Jump, then you are not allowed to try and make it Run.

So impl Jump means: can / may jump, ... and maybe even more, but we cannot guarantee that "more".

Apply this to Jump = FnOnce (callable at most once), and Run = FnMut (callable many times sequentially), and you get that:

impl FnOnce means: can be called (at most) once, and maybe even more (such as being callable multiple times), but we cannot guarantee that last part.

Now take something that impl FnMut. It can be called multiple times sequentially, so it does meet the minimal contract of being usable by someone that will call it at most once.


Regarding the documentation, I do, personally, find it badly worded. Many places in the documentation try to be "beginner-friendly" and thus over-approximate, which actually hinders the learning process in the mid to long run, since people then need to spend time unlearning the approximations.

2 Likes