Introducing Scoped Threads — A New Addition of Rust 1.63.0 | by SotoEstevez | Aug, 2022

It’s an awesome update

Let the crab threads go rampant

Rust 1.63.0 has been released and this release comes with one of the most awaited features in recent times. After all, this capability of crossbeam was one of the main reasons to implement multi-threading in Rust with this crate instead of using the standard library. Let’s see what the new scoped threads allow us.

The following code is a fairly basic multi-thread implementation. Something that could be part of any exercise of introduction to parallel programming.

We are reading the list of numbers and calculating two statistics of that list on two different threads. Finally, we wait for both results and print the results.

Read-only and join before leaving the scope, this should be safe, right?

So, what happens if we try to run this code? We get two errors like the following one, one for each thread that we spawned:

error[E0373]: closure may outlive the current function, but it borrows `numbers`, which is owned by the current function
--> src/
7 | std::thread::spawn(|| numbers.iter().sum::<i32>() as f32...);
| ^^ ------- `numbers` is borrowed here
| |
| may outlive borrowed value `numbers`
note: function requires argument type to outlive `'static`
--> src/
7 | std::thread::spawn(|| numbers.iter().sum::<i32>() as f32...);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `numbers` (and any other referenced variables), use the `move` keyword
7 | std::thread::spawn(move || numbers.iter().sum::<i32>() as ...);

It says that the closure invoked with the thread can outlive our reference. It gives the tip of using move to give the ownership of the reference to the thread, that way we know that it will not be outlived for it. The thing is that we want to keep using our list in the current scope and we also want to pass it to the other thread. We could just use Clone but we know that it shouldn’t be needed, so why can’t we keep using the borrowed list?

We are not mutating anything. And we know that we are using the join when numbers is still in scope. So, we know that is safe and should work. Why does this happen? Because std::thread::spawn doesn’t have knowledge about the scope and it assumes that our borrowed list of numbers could be dropped before the thread finishes the calculations. That’s why it requires a move or 'static lifetime. Now, however, we can spawn threads with that knowledge.

With the Rust version launched today and the stabilization of this feature, we can now pass a closure to std::thread::scope to run our threads. The closure will receive a Scope as first and only argument. We can use it to spawn the threads instead of calling std::thread::spawn. These threads that we spawn will now be sentient of their scope. So, the code above would be like this with scoped threads.

This runs just fine. As you see, we moved the spawning and joining of the threads into our new closure and used the scope to generate those threads. Now our code knows that the threads will indeed finish computing inside that scope and so they won’t outlive our borrowed numbers, which is part of a bigger scope.

Finally, as an extra I’ll let you here another example of working code that wasn’t possible before. This example was provided in the Rust 1.63.0 announcement which you can read in detail here.

In this example a can be borrowed in both threads like we did with numbers without the compiler asking for a 'static lifetime, x can be borrowed too even as mutable and both of them can be used and even mutated after the std::thread::scope. Not even a join is needed. Now, this is what I call an awesome update.

News Credit

%d bloggers like this: