- In the previous section, we introduced nested functions,
and for the rest of the lesson,
we're going to switch our attention to closures.
So a closure in Rust is kind of the same idea as a lambda
in other languages.
It's similar to a nested function,
but the syntax is kind of different.
And closures are more flexible
than nested functions in a couple of ways.
First of all, a closure can infer the types
of its parameters and its return type,
whereas a nested function, you have to specify
the parameter types and the return types explicitly.
With a closure or lambda,
the compiler can guess what the types are,
so it makes your code a little bit more concise.
More importantly, a closure can capture external variables.
So if you have a closure defined inside an outer function,
the outer function might have some variables.
You can see those variables in the closure.
The closure can capture the variables defined
in outer scope, and that's very powerful,
we'll have a look at that later.
So syntax-wise, the syntax we defined in the closure
is similar but simpler than defining a nested function.
So with a closure, it starts with vertical bars, pipes.
And inside the pipes, you can define the parameters
that you pass into the closure, remember,
a closure is really like a nested function.
And inside the vertical pipes,
you can specify the parameters
that the function takes, that the closure takes.
Optionally, you can specify the types of those parameters
and the type of the value returned from the closure.
You can leave that out if you want to
and the compiler can guess.
And then the body of the closure, the actual kind of the,
you know, the nuts and bolts of the function, if you like,
you enclose inside curly brackets,
just like you would for a regular function.
So once you've defined a closure,
you can assign that closure to a variable,
and then you can use that variable
as if it were the function name.
You can use that variable to invoke the closure
and pass parameters in and get the return value back.
Right, so here's a simple example,
a closure that doesn't take any parameters.
So it is a closure and it returns a DateTime.
DateTime is just a standard Rust type.
It takes a generic type parameter, the time zone, basically.
This closure will return a DateTime
using universal coordinated time.
The body of the closure is implicitly
the return value it returns.
When you call this lambda,
it'll return the current time expressed
in Universal Coordinated Time.
Now, this variable here, get_timestamp,
is basically a pointer to this function, if you like.
So to invoke this closure,
you just basically use the variable name.
This variable name is effectively a pointer
to the closure, call that closure.
It doesn't take any parameters. There's an empty pipe here.
So we call the lambda or the closure without any parameters.
We call the closure, it returns the current time,
we then print out the current time.
So this is a simple closure, doesn't take any parameters.
Of course, you can take parameters
in a closure and you typically will.
So here's an example of a closure that takes one parameter.
So it's a closure, the pipes indicate that.
It takes a 64-bit float as an input,
like a function that takes a 64-bit float.
It also returns a 64-bit float, and what does it return?
Well, if the number you've passed in is zero,
then it just returns zero, otherwise,
it returns one divided by,
it's trying to return the reciprocal.
So for example, if I call this closure with a value five,
the value five gets passed in here,
and it returns one divided by five, which is 0.2, okay?
So here is my closure.
Effectively a function that takes a float.
So I've passed in a float, five gets passed into here.
It returns 0.2, and that return value is then displayed.
So here's a closure that takes one parameter.
A closure can take as many parameters as you like.
So here's a closure that takes two parameters.
As it happens, they're both 32-bit integers.
It returns a 32-bit integer as well.
It returns the product.
Obviously, you would never bother writing this
as a closure or a lambda, I'm just trying
to illustrate the syntax, we'll have a look
at more realistic examples later on.
But syntax-wise, it's a closure that takes two integers,
returns an integer, it returns the product
of those two numbers.
So to call the closure, when you call the closure,
I've called it prod.
This variable is a pointer to that closure,
so call that closure, pass 20 into a, pass five into b,
it returns 100, and that return value
is then printed like so.
Right, so what about a closure that had multiple statements?
Fair enough, you just have as many statements
as you want to inside the curly brackets.
The last statement here is, if it's an expression,
no semicolon, in other words, this expression
is assumed to be the return statement.
So here is a closure.
It takes in a number of seconds
as a 64-bit unsigned integer.
It returns the date and time in UTC.
When I invoke this closure, let's see.
I invoke the closure, get_timestamp_after_delay.
I pass five as the number of seconds.
It sleeps for a duration of five seconds
and zero nanoseconds, and then it returns
the time after that delay, the DateTime,
and that DateTime is then displayed down here.
If I wanted to, I could have explicitly said return
with a semicolon at the end, but it's common
just to have an expression, and that expression
is implicitly the return value
from the function or from the closure.
Right, example time.
So back to our demo project, lesson nine,
nested functions, closures.
The demo closures example, which we're going to run.
Okay, here we are then, so in main,
let's uncomment demo closures
and let's have a look at it here.
So I've got an example
of a closure that takes no parameters,
a closure that takes one parameter,
a closure that takes many parameters,
and a closure that has multiple statements,
basically, it's the code that we've just been looking at,
but I'm gonna actually run it for real.
I've imported, well, first of all,
I've imported the DateTime and the UTC types
from the chrono crate.
Obviously, that means I've got to drag in
the chrono crate here, which I have.
And I'm also going to use the duration structure,
a duration for a sleep.
And I'm going to use the sleep function
from the thread module in the standard crate.
Well, we can just run it and have a look at the output
and then we can kind of dissect the code afterwards,
cargo run.
I've got some delays in here, okay,
so there are going to be some delays that we see popping up.
Okay, so from the top, here is a closure, or rather,
here is a closure that doesn't take any parameters.
So when I call it, it'll return the timestamp like this,
date, time, and time zone.
If I wanted to, I could have instead formatted it like so,
so when I get back the DateTime, I can just format it
as a time if I wanted to rather than the whole DateTime.
But the main thing is here,
I'm calling the closure without any parameters.
And here's a closure that takes a parameter,
so this closure is the reciprocal, I called it.
So reciprocal, remember, is just a variable.
It refers to or points to this closure.
It's like a function pointer.
I invoke that closure with a value five
and it returns one divided by five, which is 0.2.
And then a closure that takes many parameters.
So like a function, when you call it,
you've gotta pass in two parameters,
two integers, 20 and five, and it returns.
The return type is i32 and the return value is 100,
and that's what's displayed here.
And then finally, a lambda, a multi-statement lambda.
I pass in a number of seconds.
So for example, I pass in the number five.
It waits for five seconds, and it did when I ran it,
and then it returns the timestamp afterwards, which I then,
so that basically is a DateTime,
which I then format just as a time,
and it displays the time like this, must be time for dinner.
So there we go, okay, so that's basically a simple example
of closures to show the syntax
of a closure that takes no parameters,
a closure that takes one parameter,
a closure that takes many parameters,
and then a closure that spans multiple statements.
In these examples, I've been specifying
the types explicitly, I've been saying the parameters
are explicitly int 32s and so is the return type.
I've specified the parameter
and the return type there explicitly.
What we'll do in the next demo
is see how we can omit type information
and the compiler can guess what the type information is.
So that's to come.