[No Audio]
Scoped threads is a relatively new feature
that is being added to Rust 1.63.0. It
allows the threads to borrow local variables
with lifetimes more effectively. The problem
with threads is that when using threads, we
don't know exactly when the closure we give
to threads spawn will run or execute,
meaning it is hard to determine when
variables borrowed by threads will be
dropped. Let us look at an example to explain
this. I will bring the relevant modules into scope.
[No Audio]
Next, I will define a vector.
[No Audio]
I will also define a variable containing some value.
[No Audio]
The scope function in the thread module allows us
to define a scope in which we can spawn
threads. All the threads that resides inside
a scope will remain in that scope and will
complete its execution in this scope. So let
us create a thread scope.
[No Audio]
We can now spawn some threads
inside this scope using the
spawn function on the variables of some_scope.
[No Audio]
I will include a simple print statement to
the body of the thread.
[No Audio]
I can access the variable of vec inside the
thread now, I will access it inside a print statement.
[No Audio]
You may note that the compiler has no issues.
Although we have not moved the ownership of
the variable inside the thread, and the thread
do not own the variable. This is because
there is no confusion with regards to the
lifetime of the variable vec in this case.
The thread scope enforces the threads to
complete within this scope, and this
ensures that this thread will complete before
the end of the main thread. Since the
variable defined in the main has the scope
equal to the main function, therefore the
variable of vec will remain in scope when
the thread executes. The thread is not
allowed to live beyond the scope in which it
is defined in this case. Let us also define another thread.
[No Audio]
I will add a simple print statement to the body.
[No Audio]
Let us mutably access the variable x
in this thread and update its value. You may
note that the compiler has no issues. Outside
the scope, I will add a simple print statement.
[No Audio]
This print statement will only execute when
the scope ends. Since the threads are inside
the scope, so it means that this line will
only execute once both the threads are done
with their execution. When the threads are
done, the variables will be available with
the main and now it can be used. So, let me
access the two variables.
I will add an element to the vector.
[No Audio]
Finally, I will print the two values inside
the print statement.
[No Audio]
Let us execute the code now.
[No Audio]
You may note that it is working fine. In
summary, the thread scope is creating a scope
in which we can spawn new threads. Those
threads are automatically being joined at the
end of the scope. When all the threads have
completed, then the entire block completes, and
therefore the scope also completes, and then
we continue with our remaining program. The
regular ownership rules related to the
mutability are observed inside a certain
scope, that is we can have only one mutable
reference in scope. We can have multiple
immutable references in scope and mutable and
immutable reference cannot coexist inside the
scope. Let us see some details of these
rules in an example. If we push an element
in the second spawn thread to the vec, then it
will give out an error message.
[No Audio]
You may recall from the section where we explained
the closures, that the closures will try to
infer based on the usage of the variable, how
the variables are going to be borrowed inside
the closure. If the variables are not being
mentioned explicitly in the list of arguments
to the closure. In the closure of the first
thread, the variable vec is being borrowed
as immutable. And now in the closure of the
second thread, we are trying to borrow
it mutably. Since mutable and immutable cannot
coexist in a scope, therefore the Rust
compiler complains. We can have many
immutable references in a scope. For instance,
I can access the vec in the second thread
also, without having any issues.
[No Audio]
Now let us see what would happen
if we do not use the
scope, but rather use simple threads not in a
certain scope. I will use the simple thread
spawn function for creating the new threads.
First I will comment out the thread scope and
the second line, where I use the scope to
spawn the threads.
[No Audio]
I will use simple thread function.
[No Audio]
Next, I will also comment out the
scope based spawning of the second thread,
and we'll use simple code for spawning it.
[No Audio]
You may note that the compiler is complaining
in this case. Let us see the errors in this
case. It says closures may outlive the
current function, but it borrows vec, which is
owned by the current function and may outlive
the borrowed value. What this is saying
effectively is that, the closure is going to
live longer, than the variable vec is
potentially going to. Because the closure
doesn't have ownership over the variable vec,
so it doesn't have any control over how long
it lives. So what might happen in this setup
is that, this thread might spawn and then the
main may take up turn and goes to completion
thereby dropping the variable of vec.
Because this function no longer needs it, if
it happens before this thread has finished,
which can happen in this scenario with this
version of the thread, then the thread may
not be able to do its job correctly. One way
to fix this is to use the move keyword before
the closure. But with the move, we are not
being able to use the variables afterwards, as
it moves the ownership into the closure. Of
course there are other fixes also, but each
comes with its own limitations. The scope
represents a nice solution where the
variables are either reference immutably or
immutably, and the ownership remains with the
main program. That brings us to the end of
this tutorial. I hope you would have enjoyed
this. See you again and until next tutorial
happy Rust programming.
[No Audio]