- In the previous section,
I described that a closure can capture a variable
by reference,
or by value.
We're going to see how to capture by value in this section.
But first of all,
a quick recap of how capturing by reference works.
So here look at this example.
I've declared a string b1,
I'm going to draw this,
so you can kind of visualize what's going on.
b1 is a string,
which points to some text.
The closure captures b1.
It uses b1.
So remember a closure is actually an object.
When you write the closure,
under the surface the compiler creates a class
in an object,
a closure object.
And that closure object will have instance variables
that give you the external state.
So the compiler will generate a closure object
which has a b1 instance variable.
The compiler has to decide
whether b1 should be a reference,
or something else to the original string.
And it depends on how you've used b1 in the closure.
So in the closure here,
you can see all I've done is to print it.
And when you print a value,
it just captures,
it just basically does an immutable reference.
So the compiler would basically set b1
up as a as just a reference to the external string.
So the lambda doesn't own the string
it's just a reference to the string.
And obviously after my closure's finished
I can still access the original string in my main code.
We still own b1.
We haven't lost it,
we captured by reference.
Now, it's also possible to capture by value
where instead of the closure being a reference
it actually somehow copies the value itself.
So capturing a variable by value
this is necessary if the closure requires ownership
of the object,
not just a reference.
So if there's something that you do inside your closure
where it requires to own the object,
not just to have a reference to it,
in that case Rust will capture the variable by value.
Rust will actually move ownership of the variable
from the outside into the closure.
After the closure body,
the outer function can no longer access the variable.
It loses ownership okay?
So that's quite profound.
The closure requires ownership of the variable
and the external function can no longer access
that variable.
It's basically being given away into the closure,
like a donation.
So this is quite a tricky thing to imagine.
I'm going to show you an example
and I'm gonna draw a picture as well.
So here we have a message string.
So if I draw that here,
message is a string object which contains hello like so
and then I have a closure.
And in the closure,
it first of all prints message,
fair enough.
But also it calls this function here
which you probably haven't seen before.
In the standard crate,
there's a module called mem.
And in the mem module there's a function called drop.
And when you pass an object into the drop function,
it basically acquires ownership of that object.
And at the end of the function it drops it.
Okay?
So whatever you pass,
whatever object you pass
into the drop function will be dropped
or de allocated at the end of the drop function.
Okay?
So as you can see,
the closure calls the drop function
which requires ownership of message.
Right?
So let's think about what happens there.
So if I draw my Lambda object here,
it's accessing message.
So the compiler has to decide
whether the lambda should have a reference
to the original message or
whether it should be a copy of some sort.
And because the drop function requires ownership of message,
then the compiler will actually,
create a whole string object here.
It's like when you copy
or when you assign one string to another.
The lambda will actually be an assignment effectively,
it'll copy the bit pointer into here
and the original message declared
in the external scope is kind of moved away.
All right?
So a move because very similar
to when you pass an object by value into a function.
The receiving function acquires ownership
and the original variable is moved away.
So after the closure,
we can't use the original message anymore.
We can't use this original message
because its value has been moved away into the closure.
When you have a closure that moves by value,
you can't then use it.
You can't then use that external variable anymore,
afterwards.
So let me run an example to show
that in practice and we'll see the error messages you,
you get if you try.
Right so here's the code and in main I'm going
to un-comment this statement here on line 18,
demo closures where it captures a value.
And then it's here.
I've actually got two examples.
I'm capturing a value automatically,
which is what we're going to look at.
And then just before we finish this section,
we'll see how to capture a value forcibly.
Okay?
We'll ignore that for now
and we'll just look at the main function
and up here
I'll comment out the second call.
We'll just see how to capture a value automatically.
Oh, and by the way,
just to give my application a little bit of predictability,
I have a sleep for 10 seconds at the end,
just so that the application doesn't terminate too quickly
for the discussion.
Right so let's have a look
at capturing a value automatically.
Let's go through the code here carefully.
I declare a message
and I own the message and I print it.
Fine.
Here's my closure.
The closure calls the drop function
and the drop function the compiler will understand,
requires ownership of message.
So immediately the message object is moved away
from here into my closure object.
My closure object now owns message and I don't.
So I can't use message after the closure.
Okay?
Its value has been donated into the closure.
I've given it away.
I can't use it again myself.
If I tried to,
let's see what would happen
if I un-comment line 27,
gonna get a compiler error.
Right.
cargo,
I just do a cargo check.
The error message is quite lengthy
as we are kind of used to now.
So it kind of goes on a bit.
But the essence of the problem,
the actual error is on line 27.
I can't use message
because it's already been moved away basically.
And it mentions the drop function here.
Because the drop function requires ownership,
then the message was moved into the closure.
So it's been moved away from here.
I can't use message afterwards, right?
Well, so you have to be careful,
if you have a closure that requires a movement
which doesn't happen that often,
but it can you can't then use the original variable
after the closure.
There's another error message that we could get though,
if we tried to call the consume message more than once.
So I'll call the consume message function the first time.
Okay?
Which will cause the message to be dropped.
I can't call it a second time.
Let's see what happened if I did try to call it twice
Cargo check.
I'm gonna get a different error message this time.
So on line,
the error is actually on line 33 leading up to the error.
It said on line 30,
you called the closure.
And because the closure required ownership of message,
a movement occurred.
And if you then try to call consume message again,
well what would happen if it let me do that?
The first time I called consume message,
it would basically drop the message.
The message object no longer exists.
And if I then tried to call the consume message again,
it would be trying to use the message
that's already been dropped from the first call.
So in Rust,
we have the concept of closures,
that can only be invoked once
because when you call them,
you kind of destroy the context.
You drop external variables.
So you can only call this closure once.
I'm gonna explain the concept in detail later on.
It's to do with traits.
It says here,
that the closure implements a trait called fn once.
So I'll explain what that means later on.
If you ever have a closure that implements fn once
it means you can only call it once.
Okay that's technically a bit more complicated than that
but then that's what it boils down to.
So be careful.
If you have a closure that acquires,
captures a variable and then owns it and destroys it,
you can't use that variable afterwards and you
which means this statement wouldn't work.
Have to call that one out.
And you can't call the,
the closure twice either.
So anyway having commented those lines out,
I think it should now work okay.
Fingers crossed.
So up here the message initially was displayed,
hello and then I called the closure
which prints the message again hello,
and then drops it.
There was a delay at the end,
and then it terminated.
Okay so that's capturing a value automatically.
And what we're gonna look at to just wrap
up the demo is we'll see how to capture a value forcibly.
Okay so I'll just get my demo ready
and we'll have a look at the theory first
and then we'll run the demo to see what happens in practice.