[No Audio]
We will talk about Closures in this tutorial.
So far, we have talked a lot about the
functions and have used them quite a lot in
our examples. One of the key property of the
functions that we have used so far, was that,
they were having names. Rust also allows us
to create anonymous functions which are
functions having no names. These no names or
anonymous functions are called Closures by
themselves. Closures aren't all that
interesting, but when you combine them with
functions, they takes closures as
arguments, really powerful things are possible.
I believe you would have already
developed some excitement about closure,
so let's get started.
We will start with the basic syntax of closures.
[No Audio]
Any inputs to the closures are mentioned inside
the two pipes or vertical bars followed by the
body of the closure, which is represented
using the two curly brackets. In case when
the closure body only consists of a single
statement, then we must keep adding the two
curly brackets. Let's have a simple closure.
I will declare a variable first.
[No Audio]
Next, I will define a closure with no inputs and having
only a single line in its body. Since there
are no inputs to this closure. So
therefore, there is nothing between the two
pipes, and the body will contain a
single print statement.
[No Audio]
Since we have only a single statement in the
body of the closure, therefore we are not
required to enclose the statement in the
curly brackets, we can assign a closure to
some variable for instance, I will assign
this closure to the variable of square.
[No Audio]
We can call this closure
by writing the name of the variable.
[No Audio]
Okay before we proceed further, we
should note a key point with regards to closures.
The point is that, the closure
captures its environment, this means that the
closure will capture the variables from the
code segment in which it is defined. In this
case the variable x is known inside the
closure body, this behavior is unlike functions.
Inside the body of the function
only those variables are known, which are
defined inside the body or which are passed
to the function as inputs. In this case,
although we did not pass the variable of x to
the closure, however, it is still known
inside the body of the closure. In summary,
all the variables in the code segment in
which the closure is defined are visible to the closure.
Let us now define a closure that
accepts some inputs. First I will mention some inputs.
[No Audio]
You may note that, for the
inputs, we need to mention the types as well,
as well as the tape, just like the functions.
I will update the print statement
in the body of the closure also.
[No Audio]
I can now call this function by providing suitable
inputs, I will call this with the input of x,
let us execute.
[No Audio]
It is computing the square of
the variable x just like functions. We can
always call the square closure in subsequent
code, I will define another variable y and
then I will call the closure again by writing
square but this time with the input of y.
[No Audio]
Let us execute again.
[No Audio]
That's great, it is computing the square.
This means that, we can
reuse the closure just like functions.
Now, let us look at some interesting things about
the closure. The first point to note is that
redefining a variable with a different
closure will use the new definition. Let us
elaborate this further. To explain this, I
will use the same program, instead of printing
the square, I will print a cube.
So I will update the print statement.
[No Audio]
This means now that, we have two
different closures in the two lines.
In fact, we have redefined the
variable so that it corresponds to a
different closure. Let us see which closure
will be called by the call to square by
executing this code.
[No Audio]
You may note that, in both the calls, the
second definition is being called, and the
code is displaying to us the cube of the value.
The ownership rules in case of
closures are applied in pretty much the same
way is in connection with the functions.
Let us look into the details.
I will first define a closure which will display the
general_info name and age that it will
receive as an input.
[No Audio]
I will assign this closure to some variable.
[No Audio]
Next, I will define some variables whose
values will be passed to the closure.
First, I will define general_Info.
[No Audio]
Next, person_name,
[No Audio]
and finally the person_age.
[No Audio]
I will now call the closure with these variables.
[No Audio]
In this case now, the general_info
variable ownership will be
transferred to that of the variable inside
the closure, while the variable a person_name
ownership will remain with
the variable of person_name and
will not be transferred to the variable of
name inside the closure. Lastly, the variable
person_age ownership will also be
transferred since it is being passed by value
and not by reference. Let us execute the code.
[No Audio]
Now, if I add another print statement where I
print the value for the variables which are
being sent out by value after the closure is
being called, so the Rust compiler will
complain since the ownership of those
variables have been transferred, let me
include a print statement.
[No Audio]
You may note that, the compiler is not happy
saying that borrow of a moved value. Okay.
You may however, note that if I change the
variable to date of person_name,
there won't be any issues.
[No Audio]
This is because, in
this case the variable ownership was not been
transferred, as we have used a reference to
this variable. In summary, we have to pay
attention to the ownership rules when we are
passing values to the closure. Okay, let us
move to the next point. The closures are able
to infer the outputs and inputs themselves
and it is unlike functions where we have to
explicitly mention and set up the inputs and
output types. Let us elaborate this in detail.
I will start with a simple closure
for computing the square of a number.
[No Audio]
You may note that, currently the input and
output types are unknown for the closure.
Next I will define a variable x and we'll
call this closure with the variable x.
[No Audio]
You may note that as soon as I call the
closure, the type will be updated for the inputs.
Now once the inputs are being set and
inferred by the Rust based on the first call
to the closure, they will be kind of locked.
If we call the same closure with
different types of inputs the Rust compiler
will complain. For instance, if I declare
another variable of y having a float value,
and we'll use it to call the closure.
[No Audio]
You may note that, the compiler is not happy. This is
because the first call to the closure sets up
the inputs and output types and the Rust
assumes that subsequent calls to the same
closure will use the same input same output types.
If I comment out the first call,
you may note now, that the inputs are being
updated accordingly. Okay, now, let us move
to the next topic, we can also pass the
closure to a function as a parameter.
In this program, we will write a function which will
be used to compute the division of two numbers.
Moreover, the same function
will use a closure to check if the
denominator of the fraction passed to the
function is zero or not. So, let us start to
code this program, I will first define a closure.
The closure inputs will be a float
number and it will return a bool value, which
is true when the number representing the
denominator is not zero and false otherwise,
let me add the code.
[No Audio]
Okay, next I will define
the function of division with three inputs.
The first two inputs will be numbers and the
third input will be a closure, which will be
representing using generics.
Let us set up the inputs.
[No Audio]
We will also need to mention the same generic
just after the name of the function, I will
set its type to that of a function with the
input of f32 and the output of bool.
[No Audio]
This tells the Rust that we are using f as a
generic but it's type will be a function,
which will accept an i32 as an input and
will return a bool value.
Inside the function
I will first make a call to the closure to
check if the denominator is zero or not. In
case it is 0 I will print it the division
is not possible and in case it is not 0, I
will print to the user the result of division.
So let me add the code.
[No Audio]
Let us use this function now. I will call it
with the input 5 and 10 and the third
input will be the variable which we can use
for calling the closure which in this case is
the variable of division.
[No Audio]
Inside the body of the function the statement
of f of y means that we are calling the
closure division_status. In other
words, the f of y has the same meaning as
that of division_status with the
variable y as input.
Okay, let me call it one
more time with a second parameter as that of
0 or the denominators is set to the value of 0.
[No Audio]
Let us cargo run this to check the result.
[No Audio]
For the first call, the result is the
division have the two numbers, while for the
second call the result is the print message
that the division is not possible. Okay, that
brings us to the end of this tutorial, we
have learned about the closures and how to
use them in different ways. In the upcoming
tutorial, we will be covering more
interesting concepts about the closures and
its capturing environment. So do come back to
cover that event and then
happy Rust programming.
[No Audio]