- As we saw in the previous demo,
a thread closure, if it's going to capture external state,
must do so by moving ownership into the closure.
So this is the example we saw
in the previous demo and it works fine.
I've got a vector here, V,
and my thread, which spawns disclosure,
I've got a for in loop in here,
and when you use a for in loop with V,
like I've written it here,
then this for loop requires ownership of V
which means V will be moved into the closure, okay?
So the closure will acquire ownership of the vector.
And that's good because
if this external function disappears quite quickly,
if it returns quite quickly,
it's important that the closure owns it's data
so that if it outlives the function
it still owns the data that it's using.
Right.
So a thread closure is not allowed to capture external state
by borrowing it into the closure.
So we'd get a compiler error if we tried.
The compiler will actually stop us
from trying to borrow into a thread closure.
Have a look at this example.
It's quite similar.
There's a key difference.
I've got an ampersand here instead of a just V.
So the code I've tried to write,
here's my external vector, and in my thread closure,
I've tried to capture a reference to V.
I've tried to borrow V, okay?
But the compiler won't allow it.
The compiler will actually give an error message here.
It'll say a thread closure.
Even if you try to borrow a thread closure,
it's not allowed to borrow,
because you'd end up with a reference
to something that might not live long enough.
As I've said, if this thread
is going to last for 10 seconds,
but the external function is gonna terminate quite quickly,
if it let us do this, the compiler,
we'd end up with a reference
to something that's no longer there, a dangling reference.
This is actually enforced at the language level.
You're not allowed to have dangling references in rest.
And that's part of the ethos of rest, really, is safety,
stopping us from doing things
that other languages have let us do,
but then penalize us by crashing.
Right, so what do you do then?
What if you have got code like this
which is attempting to borrow,
but it's not allowed to borrow?
In that kind of case
where you could get away with borrowing state,
the compiler insists that a movement occurs,
you can actually force the compiler to do a move.
There's a move keyword, would you believe,
which you put here,
you put it before the closure when you spawn it,
and regardless of how you're using the variable here,
I mean technically, I'm using for item
and I've got a reference to V here.
So in terms of actual usage, the compiler would guess
that a borrower would be sufficient.
Of course, the other part of the compiler would say,
oh no you don't because borrowing is dangerous
because of dangling references.
So to avoid that other part of the error,
we can use the move keyword.
The move keyword is where
under other circumstances, the compiler would give an error
because borrowing isn't really very sensible.
The move keyword will force a move to occur, okay?
So this factor will be forcibly moved
and therefore claimed ownership inside the closure.
So even if this external function
terminated almost immediately,
the data would be moved forcibly into disclosure
and it would live long enough
for that thread to use it.
Right, so only one extra keyword in this example,
but quite a profound difference.
So let's run the demo in the usual project.
We're going to look at main,
and then we'll have a look at demo_capturing_state
using an explicit move
where we have to forcibly tell the compiler
to move data to avoid dangling references.
So main, this is the code.
I'm going to do run demo_capturing_state
via an explicit move.
And then here's the code.
So I've gone into overdrive with my comments here.
I hope it's grammatically correct.
So here's my main code up here for this part of the demo.
Very similar to the previous example.
I call this do_some_work function
which will spawn a thread but then terminate
or return immediately with a thread handle.
So it'll kick off this other thread
which will take a while to spin
but the main thread returns the handle immediately.
Remember, when you have an expression
at the end of a function, it's like saying return that,
but it's more idiomatic
to just read it this way and (indistinct).
Returns the handle, back up to our main code up here,
and our main code then just sits there
busy, doing nothing, really,
waiting for that thread to finish.
So let's see what that thread actually does.
Here's my vector with elements 100 to 105.
Okay.
So as before, when you have a closure,
which captures a variable,
and we do have a closure, which captures a variable,
V is the external variable, and we are capturing it here,
so the compiler will analyze usage by us
of that variable in the closure
to decide whether a borrow, a multiple borrow
or a move would be required.
So the compiler infers how to capture V,
dependent on how it's used in the closure, right?
And that's what I just said.
In this example, the compiler would guess,
I'm ignoring the move keyword for now, okay?
So let's ignore that for now.
So in the usage that the compiler sees
in the closure as it stands,
the compiler would guess that a borrow would be sufficient
because the for end loop now just borrows vector V.
It borrows it rather than actually
acquiring ownership of it.
It just passes a reference into the for loop.
And that's the problem that the move keyword resolves.
If the compiler did decide that a borrow was sufficient,
this would give us a different compiler error
because of thread timing.
The closure of the thread
might outlive the scope of the current function
and then we'd have a dangling reference to V
externally that it disappeared.
So if I didn't have this move keyword,
and actually get an error from the compiler,
should we see that first?
I'll delete that move keyword,
because let's say I didn't know about it, okay?
So I've just got that code.
I'm gonna get a compiler error
and the compiler error will occur...
Well, difficult to tell, actually.
I think the compiler's gonna give me an error
on line 22, would be my guess.
Let's see.
Yeah, there we go.
So when I spawned the thread,
there's the start of my closure,
it realizes, looking at it here,
the compiler on the one hand
would think a borrow would be sufficient,
and then it says, okay, so that's a reference
to something external.
You're referring to something you, the spawn thread,
might outlive the borrowed value V.
Okay?
so you borrow it here.
What if your thread outlives V?
Then you're left with a dangling reference.
So doesn't like it at all.
But being rest, it doesn't just beat you about the face,
it tries to be constructive.
And it says maybe to force the closure
to take ownership of V and anything else,
use the move keyword.
I've never come across a language
which makes in the compiler,
it makes such an effort to actually explain stuff.
It's kind of the opposite of the C++ compiler
which is as terse as it could possibly be.
It suggests adding the move keyword here.
Well, that seems like a good idea, so let's do it.
Move.
So regardless of the usage pattern
that the compiler might think, reference, no,
it's gonna be a movement
in order for us to own the data inside the thread
for as long as the thread needs it.
Of course, that's a double-edged sword
because now that that vector has been claimed,
ownership has been claimed by the closure,
it's been moved away from the outer function,
we can't use this variable anymore,
and we discussed that previously, didn't we?
Now that that variable's been moved into the closure,
we can't then use V afterwards,
and we've seen what would happen if we tried.
You get a compiler error saying,
but that value's moved away.
Get your act together, dude.
Anyway, I think we're in a good state to run this now.
Do some work.
Here's the function I've called.
(indistinct) handle, pretty quickly
it'll return the joint handle at the bottom.
So the main thread can wait.
Inside here, having captured V,
it's quite happily going to be displaying the values
in a 500 millisecond kind of delay.
Okay, so should we just run it?
And I think I fixed all the bugs now
that I've been mentioning.
Let's just see if it works.
Yeah.
So my inner thread, my background thread was busy.
Every 500 milliseconds, it was displaying those things.
Then it terminated, and my main thread then resumed
and that's the end of that.
That's all, folks, for this demo at least.