Random Rust Notes - 4
Tags: rust, scopedthreads
Published: 2023-04-29 17:25:34

---
title: Random Rust Notes - 4
published: true
description: A collection of notes I have made, while learning Rust
tags: rust, scopedthreads
# cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8cdf2b7imrskwy153y2.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-04-29 17:16 +0000
---

[Cover image credit: https://pixabay.com/users/engin_akyurt-3656355]

This blog is a collection of notes of key learnings / understandings / *Aha* moments, as I am kayaking through demanding yet exciting waters, of Rust ( #rustlang ). If you happen to find defects in the code or gaps in my understanding, please correct me. I will remain thankful.

The topic of this particular notes: _scoped_threads_ in Rust.


### A brief on spawned threads

Using the standard library, whenever a thread is launched - `spawn`-ed - a unit of execution - - with an associated piece of logic - is created.  Underneath, the library, OS facilities and hardware work together to run that _unit of execution_ , **separately** from the unit of execution that launched it. This is at the core of threading - a bit simplistic, I agree  - concept. One is the parent thread and it launches a child thread, as is commonly understood.

Naturally, the child thread will finish its job at some point in time. The parent thread may need to know if and when that happens (let us keep away from the aspect of *cancellation* of the child thread for the time being). A mechanism must exist for it to be able to do that. This mechanism takes the form of a handle (programmer-speak). Using this handle, the parent thread waits for the child thread to `join` the former and then, the flow of execution continues.



### So, we need a handle

```rust
pub fn start_as_free(&self) -> JoinHandle<()> {
        let message_collector_handle = thread::spawn(move || { () });
        message_collector_handle
    }
```

Here's a skeleton code: a thread is `spawn`-ed and its handle is returned to the caller (the parent thread). For a better understanding of what that `JoinHandle` is, here's the standard library's [documentation]([JoinHandle in std::thread - Rust](https://doc.rust-lang.org/stable/std/thread/struct.JoinHandle.html)). `JoinHandle` is a structure of type `std::thread::JoinHandle`. One of its implementation functions is named join, `pub fn join(self) -> Result<T>`. This is the mechanism available to the parent thread, to wait for the child thread to finish. From the standard library's documentation:

### Now, a scoped thread

Instead of spawning a free-running thread as shown above, a parent thread may launch a *scoped* thread. A scoped thread, is built in a way which ensures that the function in which it is launched will finish only when the threads launched inside it are also finished. This is a key aspect because a scoped thread cannot *live beyond* the demarcation laid out by its parent thread. Therefore, the parent thread may share its own data structures with the child thread, secure in the understanding that all the changes (if any) done to these data structures by the child thread, are guaranteed to be visible to the parent. 

From the standard library's documentation:

```rust
pub struct ScopedJoinHandle<'scope, T>(_);
```

So, `ScopedJoinHandle` is a structure, much like its elder sibling `JoinHandle` (elder, because it preexists). And, sure enough, it has a `join()` function as well. But with a twist!

### Difference between spawning steps

A standard, your-friendly-companion-thread is spawned thus:

```rust
 let message_collector_handle = thread::spawn(move || { () });
```

A scoped, strictly-watched-and-controlled thread, is spawned thus:

```rust
let message_collector_handle = thread::scope (|current_scope| 
                               {
                                  current_scope.spawn(|| { Ok(()) });
                               }
```

Something special is happening in `std::hread::scope()` . From standard library's documentation:

```rust
pub fn scope<'env, F, T>(f: F) -> T
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T,
```

So, the `scope` function takes in a closure as a parameter, which in turn takes in a `Scope` (with uppercase 'S')  value as a paremeter.

What is that `Scope`? It is a struct! From the standard library's documentation:

```rust
pub struct Scope<'scope, 'env: 'scope> { /* private fields */ }
```

And, as expected, this struct implements a `spawn` function:

```rust
pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>where
    F: FnOnce() -> T + Send + 'scope,
    T: Send + 'scope,
```

Let us not get lost in that crowd of symbols. The key understanding:

* `thread::scope` takes in a closure. This closure, in turn, takes in a `Scope` value. Inside the closure, a thread is spawned: `Scope::spawn()`. This `spawn()` call returns a `ScopedJoinHandle`.

* The return value of type `ScopedJoinHandle` is valid as long as the parameters are valid. Put another way, the return value (remember, this is a `struct` ) cannot live even when the `Scope` (the struct, starts with an uppercase 'S'!) doesn't.

### An example makes it clearer, hopefully!

Let's watch these two functions, being compiled:

```rust
pub fn start_as_scoped(&self) -> ScopedJoinHandle<Result<(),String>>{
        let message_collector_handle = thread::scope (|current_scope| {
                    current_scope.spawn(|| { Ok(()) })              
                });

        message_collector_handle    
    }

// Here's our old friend, std::thread::JoinHandle
pub fn start_as_free(&self) -> JoinHandle<()> {
        let message_collector_handle = thread::spawn(move || { () });

  message_collector_handle
}
```

The compiler is watchful, yet helpful:

```rust
error: lifetime may not live long enough
  --> src/main.rs:30:21
   |
29 |         let message_collector_handle = 
                thread::scope (|current_scope| {                                            
                                -------------- return type of closure is ScopedJoinHandle<'2, Result<(), String>>
   |                            |
   |                            has type `&'1 Scope<'1, '_>`
30 |                     current_scope.spawn(|| { Ok(()) })
   |                     ^^^^  returning this value requires that `'1` must outlive `'2`

```

Hmm! We cannot return the `ScopedJoinHandle` in a manner as we could, for its sibling, `JoinHandle` .

### But, why?

Without getting into the details of lifetime, we can analyse it intuitively. 

The `ScopedJoinHandle` cannot live beyond the lifetime of the `current_scope`. Put in a different way, we cannot let the `ScopedJoinHandle`
return from the function, because that will mean that some other thread will be able to `join` the thread launched under the watchful eyes of
`current_scope`. But that is disallowed. The standard library's documentation makes that very clear:

-------------------------------------------------------------------------------------------------------

>   All threads spawned within the scope that haven’t been manually joined will be automatically joined before this function returns.

-------------------------------------------------------------------------------------------------------


This means, that either we explicitly `join` the thread *before* leaving the scope or thread will be implicitly `join`-ed by Rust runtime, when the `current_scope` is dropped.

This compiles:

```rust
 pub fn start_as_scoped(&self) -> i32 {
        let message_collector_handle = thread::scope (|current_scope| {
           current_scope.spawn(|| { 42 }).join()    // <---- join() before leaviing the current_scope>    
         });

    
        match message_collector_handle {
            Ok(x) => x,
            Err(_) => -1 // We could have returned a String, by modifying the return type of the function to Result<T,E>
        }
        
    }
```


### Key takeaways

*   A thread which is spawned inside a *scope*, must end before scope ends.
*   Such a thread's handle cannot outlive the overarching *scope*.
*   Such a thread must be `join`-ed explicitly or those will be `join`-ed implicitly, at runtime. And, inside the scope!!

NB: Standard library's documentation on Scoped threads, is elaborate and worth a good reading!