- You can pass a closure into a function.
The closure specifies an algorithm
that you would like a function to invoke.
So in the function that receives the closure,
you specify what type of closure you expect to receive
and you do that
via the three trait types, FnOnce, FnMut, and Fn.
So the incoming function,
it has a type constraint to say
give me a closure based on one of those three trait types.
So let's see how this works.
We'll have a look at an example
of a function that receives an FnOnce
and then a function that receives an FnMut.
And then thirdly, a function that receives an Fn.
Three different types of closure
that you can pass into a function.
Okay, so first of all,
imagine a function that receives an FnOnce closure.
So if your function receives a closure
and only plans to call the closure once,
then your function should be generic.
It receives a function parameter
and that function parameter should be of type FnOnce.
It looks like this.
I've got a function,
whatever you wanna call it, I've called it receive_fnonce.
It's a generic function,
it has a type parameter.
I've called it F as in function.
So the idea is you pass some kind of function in here
or to be more specific, you pass in a closure
and that type parameter F must be some type,
which implements FnOnce, okay?
This is how you specify it implements FnOnce.
This here is the signature of a closure.
It represents a closure that takes no parameters
and returns no value.
So when you call this function,
when you call receive once, you gotta pass in a closure
and that closure must have this signature.
It can be any closure that takes no parameters
and doesn't return anything.
So func is gonna be such a closure.
We only call it once in here, so that's fine.
Right, so I can pass in when I call receive_fnonce,
I can pass in any kind of closure,
any kind of function that implements the FnOnce straight,
all closures implement FnOnce.
So I can pass any kind of closure into this function as long
as it has the signature that it takes no parameters
and it doesn't return anything.
Right, of course, when you have inheritance,
if you have a function that receives type A, remember, well,
let's say we've got an inheritance hierarchy
that looks like this.
A function that specifies it can receive type A,
you can pass in, according to the list
of substitution principle, you can pass in A
or anything which inherits from A.
So if you kind of imagine the same situation here
in terms of closures, with closures,
we have an inheritance hierarchy of traits.
We have FnOnce and then inheriting from that,
we have FnMut, and then inheriting from that,
we have Fn as we discussed in the previous section.
So a function that can receive an FnOnce,
effectively, that's kind of what we've got.
It can receive one of those objects
or a lambda or closure, which implements FnMut or a closure,
which implements Fn.
Any kind of these closures can be passed in to here.
So we're gonna have a look at that example, first of all,
before we go any further.
In the usual demo project, we'll have a look at main
and then we'll have look at demo_closures_fnonce
and we'll run it.
Okay? So here we are.
Here's my main code.
I'm gonna run three separate demos in this section.
First of all, a demo of closures
that receive an FnOnce parameter.
Let's uncomment that that, and let's see the code.
Okay, so I've got several things I need to explain here.
I'm gonna have three demos
and they're all gonna be kind of similar to each other
so I just need to explain it once.
First of all, here's a function and it's generic.
It receives some kind of parameter of type F
where F must be a closure.
FnOnce means it's a closure.
A closure that takes no parameters
and doesn't return anything.
If you wanted to receive a closure that took an integer
and return the Boolean, then you'd write this kind
of syntax, a closure that takes an int32
as a parameter and returns a Boolean.
Okay, that kind of syntax.
This here indicates the signature
of the closure that you're passing in.
So if you just write this, it means I want
to have any closure which implements FnOnce,
which basically means any closure that takes no parameters
and doesn't return anything.
And I only call the function once, so that's fine.
So a function that receives an FnOnce parameter
can receive any kind of closure.
So I've called this function three times in the code below.
I've called the function once, twice, three times.
The first time, have a look at this closure here.
That closure will be converted
into a structure by Rust and that structure,
what interfaces will that structure implement?
Will implement FnOnce, FnMut or Fn?
And the answer is this closure will just implement FnOnce.
It can only be called once.
This closure can only be called once
because it drops its external string.
We looked at this in the previous section.
A closure that captures variables
by movement can only be called once.
So when I pass this closure into the function,
it's important that that closure only gets invoked once.
Okay, so I can pass in an FnOnce parameter
into here or anything which inherits from FnOnce.
I can pass in a closure which implements FnMut
and that's what I've done here.
Okay, here I've called the function a second time,
call that function again.
And this time, that's the function.
And this time, the closure I've passed in,
now have a look at that closure.
It'll be converted into a structure
and that structure will implement
either FnOnce or FnMut or Fn.
The compiler will decide what trait it implements.
If we look at it carefully,
we can see that the structure, sorry, the closure mutates.
It captures s2 and it mutates it.
Therefore, this lambda, sorry, closure,
keep on saying lambda, my mistake.
This closure will be converted
into a structure which implements FnMut.
And because FnMut inherits from FnOnce,
I can pass that closure into here.
I can pass in anything which inherits from FnOnce,
so FnOnce, FnMut or Fn.
So this second call I can pass
in a closure that will implement the FnMut trait.
And then finally, my third call, this closure here,
it captures s3, s3 is my string.
It just prints the string, but it doesn't change it.
So this closure will implement Fn, Fn inherits from FnOnce.
So I can pass that closure in as a parameter in here.
Remember, it was expecting a closure,
which implemented FnOnce or anything
which inherits from FnOnce.
This closure implements Fn, that inherits from FnOnce,
therefore, I can pass that closure in.
So everything in here is gonna work fine.
I'll just run it to confirm.
All good.
Okay, so I could pass in closure number one
into FnOnce into my receive_fnonce function.
I can then pass in the second closure,
which changes string B to be bbb BBB.
And then I can pass in a closure, which just implements Fn.
Okay, so we've just seen a function,
which receives a closure,
which is bound to be of type FnOnce.