- When we started this lesson, we looked at nested functions
and then we looked at closures.
And I explained that one of the big differences
between a nested function and a closure
is that a closure can access variables
defined in the outer scope.
And that's what we're going to look at now.
It's known as capturing, and here's the basic idea.
I've got an outer function, which declares some variables.
I've got a closure.
And that closure can access captured variables.
It can access variables defined in the outer scope like so.
Okay, so that's called capturing, and it's very useful.
It allows this closure to benefit from information
defined in the external scope.
You can use these variables inside the Lambda.
Okay, and you can't do that with nested functions.
So, how does capturing work with closures?
Basically what the compiler will do
is it'll decide what mechanism to use
to capture the variables
depending on how you've used them in your closure.
In your closure, if you've used an external variable,
the compiler will either capture it
as an immutable reference
if you don't change it and don't need ownership.
Or it'll capture the external variable
as a mutable reference if you are changing it.
Or under certain circumstances, it'll capture by value.
And that's quite tricky.
Basically what that means
is that the external function had a variable,
but ownership gets transferred into the closure.
So, the closure now owns the variable
which means you can't then use it anymore
in the external function.
Okay, so that's a bit tricky.
We'll have a look at that later.
So, the idea is the Rust compiler looks in your closure.
It sees how you've used the variable
and that's how it decides how to do the capturing.
So, we'll have a look at capturing references first
because they're easier.
And then, later we'll have a look at how to capture values.
And we'll discuss all the kind of implications
in terms of ownership in that case.
So, here's an example of capturing a variable
as an immutable reference.
So from the top, I've declared a string b1.
It's like a banner.
And it displays the top part of a banner.
And I've got another string b2
which displays the bottom part of a banner.
And I've got a closure, which uses b1, and it uses b2.
It displays a heading with a nice banner around it.
It also takes a string as a parameter.
I call it twice.
First of all, I pass in "hello" into the string.
And then, second time I pass "goodbye" into the string.
So, let's see what happens.
When I call the closure the first time,
I pass in "hello" into s.
At that point, my closure is going to print b1
Now, when you print something, print borrows immutably.
It doesn't change the value.
It's an immutable borrow.
So, the compiler will realize that our closure
needs to capture an immutable reference to b1.
So, effectively it looks like this.
The compiler will capture
an immutable reference to b1, okay?
And then, it'll display the heading,
this kind of upper banner here.
Then, it displays the string,
whatever I pass into the string, I passed "hello" into s.
So, it prints kind of like side pipes
and then it prints the string
in a field with the 15 characters, left aligned.
And then finally, I've borrowed b2.
And again, this is an immutable borrow.
So, the compiler will capture b2 immutably.
Okay?
Now what actually happens internally is quite interesting.
Closures internally are objects.
And when you capture an external variable,
they become instance variables inside that object.
So, this closure here actually gets converted into a class
and that class will have an instance variable
to capture b1
and that will be an immutable reference like so.
And when it sees, when the compiler sees
that you've used b2, it'll add b2 as an instance variable
into the closure object
as a immutable reference to b2 like that.
And then, when you invoke the closure,
it basically kind of calls,
it's similar to the way it works in C++.
If you've looked at operator parenthesis in C++,
it's a similar kind of mechanism to that.
When you call a closure, it'll basically invoke the code
that you specified to print the message
and using b1 and b2 to access the external variables
as immutable references.
All right, so one other thing
and then I'm gonna run the example.
Here, a reference is sufficient.
The closure hasn't claimed ownership of b1 and b2.
They're just references.
So, the external function still owns b1 and b2,
and can still use b1 and b2 afterwards, okay?
We haven't lost ownership.
We've just, they've just been borrowed by the closure
but the outer function still owns b1 and b2
and can still use them.
Okay, so let's run the example to show that in practice.
So, here's the example.
In the main code,
I'm gonna un-comment this part of the demo,
demo_closures_capture_reference.
Very good.
And it's here.
So, I've got two functions.
The first function
is what we're going to look at first of all,
where I've got my b1 and b2 strings.
My closure, which captures b1 as an immutable reference.
It's all kind of implicit really.
You just have to read between the lines
and understand that b1 will be captured by the closure
as an immutable reference and printed.
Then, it'll print the string.
Then, it'll print b2 which is the bottom part of my banner.
And that's my closure, which will print a banner
for "hello" and a banner for "goodbye."
And then just to emphasize that the outer function
can still access b1 and b2 afterwards.
The closure hasn't claimed ownership of b1 and b2.
It's just borrowed the values via references,
immutable references in this case.
So in my main code, I've got two calls.
The one we're looking at here
is capture_immutable_reference.
We'll have a look
at capturing mutable reference a bit later.
So, let's just ignore that for now.
Let's just comment that statement out.
And we'll just run the first part of the example.
I was quite pleased with this example actually
when I decided how I was going to do it.
It looks quite nice, cargo run.
Okay.
So, it displayed the heading for "hello"
which meant displaying b1
which is the top part of the banner.
And then, displaying "hello"
and then the bottom part of the banner.
So, there's hello in the bottom part of the banner.,
And then, I repeated it to display the goodbye banner.
So, b1 goodbye, and that's the end of b2.
And then, just it prints b1 and b2 on separate lines here,
just to confirm that we can still access
b1 and b2 if we want to.
So, because this closure captured b1 and b2,
but it didn't need to change them,
b1 and b2 will be captured as immutable references.
Good.
What we're gonna look at next
is how to capture multiple references.
So, let me just rearrange my demo.
Let's discuss the theory and then we'll run the demo again.
Okay, so have a look at this example.
First of all, note that b1 and b2 are mutable.
They could potentially be changed anywhere.
And in my closure,
if you want to change a captured variable,
then you have to declare the closure itself
has to be multiple.
Right. Okay.
So, my mutable, sorry my closure
is allowed to change external variables.
And it does.
b1, it captures by reference,
but it's gonna change b1.
It's going to append a tick mark at the end of b1.
Okay, so first of all, obviously b1 has to be mutable.
And because I want to change b1 in my closure,
then the closure itself has to be mutable.
So, what'll happen is, again,
internally when you have a closure,
internally it is actually a class one object.
And when you access b1,
it'll basically access,
it'll in my Lambda object, it'll basically create b1
as a mutable reference to this string here.
And then, obviously when I capture b2,
it'll capture b2 also as a multiple reference.
Okay, so there we go.
So, when I call the function, my Lambda I should say.
The first time I pass "hello" as the string
and it appends a tick mark at the end of b1.
I just draw a tick mark there.
And it appends a tick mark at the end of b2
I'll put a tick mark there.
And then it prints the banner as it currently looks.
And then, I call, I just tidy my diagram up a little bit.
And then I call the closure a second time,
and this time I pass in "goodbye."
So that string gets passed into this, into s.
And then it depends, another tick mark into b1
and another tick mark into b2.
So, it has actually changed the value
in the external banners.
And then, it prints goodbye with those banners.
And then at the end, just to show
that we still own b1 and b2 in our external code,
we can then print out b1 and b2 separately.
And they will have changed by the closure.
Let's run that example.
So, capture_mutable_reference.
Here's the code that we just looked at.
Let me just tidy up my output and run it again.
Okay, so b1 was originally that banner.
When I call the closure the first time to print "hello",
it'll add a tick mark at the end of b1 and b2,
and then print out the message.
So, b1 has a tick mark, and b2 has a tick mark.
I call the closure a second time with "goodbye".
It adds another tick mark, so that's two tick marks.
So, b1 now has two tick marks.
And b2 also has two tick marks.
So, at the end of the code
when I print off b1 and b2 at the end,
you can see that they have indeed been changed
by the closure, and this is absolutely fine.
It's perfectly reasonable for a closure to be mutable
and to change the external state, okay?
Don't feel nervous about that.
That's perfectly allowable.
Just remember that if you do want your closure
to mutate captive state,
the closure must be defined as mutable, okay?
Otherwise, it won't work.