Rust Notes on Temporary values (usage of Mutex) - 3
Tags: rust, mutex, scope
Published: 2023-06-21 06:50:31

---
title: Rust Notes on Temporary values (usage of Mutex) - 3
published: true
description: A collection of notes I have made, while learning Rust
tags: #rust, #mutex, #scope
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fieo0rdpxxt5daynrb4i.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-06-21 06:55 +0000
---

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

### Build up

Perhaps, it will be easier to understand the background of this article, if the ones just preceding it (in the series) are referred to:

1. Rust Notes on Temporary values (usage of Mutex) - 1  ([link](https://dev.to/nsengupta/rust-notes-on-temporary-values-in-method-call-expressions-1-1g8))

    Explores Method-call expressions and binding.

2. Rust Notes on Temporary values (usage of Mutex) - 2 ([link](https://dev.to/nsengupta/rust-notes-on-temporary-values-usage-of-mutex-2-403p))

    Explores RAII, OBMR and how these are used in establishing the behaviour of a Mutex



Mutex is used to fence a piece of data. Any access to this data, is preceded by acquiring a lock on it. Releasing this lock is crucial, because unless that happens, subsequent access to this data will be impossible. In the preceding article, we have briefly seen how a combination of RAII (or OBMR) and Scoping rules of Rust, makes this very straightforward. However, are there cases where the lock is held for longer than we expect?

### Revisiting accessing the fenced data

We have seen this snippet earlier.


```rust
use std::sync::Mutex;
fn main() {
    let a_mutex = Mutex::new(5);
    let stringified = a_mutex.lock().unwrap().to_string(); // multiple transformations here!    
    println!("stringified -> {:?}", stringified);
    println!("Mutex itself -> {:?}", a_mutex);
    println!("Good bye!");
}
```

`a_mutex.lock().unwrap().to_string()`: In that expression, multiple steps are taking place:


*   Lock is obtained from `a_mutex`. An unnamed `MutexGuard` object is produced.
*   The `MutexGuard` is unwrapped and its internal `i32` ( value of '5') is made available (through a mechanism of *deref* but that is not important here)
*   The `to_string()` method available on `i32` is called.
*   A stringified representation of numeric '5' is produced. This is the final value of the expression.
*   Then the expression is complete and the statement ends. Unnamed `MutexGuard` goes out of scope and hence, dropped. The lock is released.

This stringified representation is assigned to (bound to) `stringified`. 

Importantly, just before that **;**, the scope of unnamed `MutexGuard` ends, and it is dropped, thereby releasing the lock. That is as well, because once we call `to_string()`, our objective has been fulfilled.

### Can we force the guard to stay open for longer?

The `MutexGuard` is dropped when the statement ends. What if we want the guard to exist, till we want? 

We have already seen this:

```rust
use std::sync::Mutex;

fn main() {
    let a_mutex = Mutex::new(5);
    let guard = a_mutex.lock().unwrap(); // <-- 'guard' is of type `MutexGuard`
    println!("guard -> {:?}", guard);
    println!("Mutex itself -> {:?}", a_mutex);

    // An attempt to acquire the lock again.
    let w = a_mutex.try_lock().unwrap();
    println!("Good bye!");
}
```

The output clearly shows that the mutex is in a locked state and an attempt to acquire the lock again is disallowed:

```bash
guard -> 5
Mutex itself -> Mutex { data: <locked>, poisoned: false, .. }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "WouldBlock"', src/main.rs:37:32
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

Why? Because the `MutexGuard` has not gone out of scope. It is now bound to a variable `guard`; therefore it is not temporary anymore. In fact the `MutexGuard`'s scope now spans till the end of the block, i.e. end of `main` in this case.

Clearly, the scope of `MutexGuard` ends when we operate directly on it by calling a method on value it is *guarding* but not when we bind it to a variable, and then operate on it. Does any other case exist, where the scope is extended?

### Making use of `match` expression

`lock()` returns a `LockResult`; therefore, we can *match* on it:

```rust
use std::sync::Mutex;
fn main() {


    let a_mutex = Mutex::new(5);
    let stringified = match a_mutex.lock() { // <--- scope begins

        Ok(guard) => {
            let v = guard.to_string();
            println!("Mutex itself on a match arm -> {:?}", a_mutex);
            v
        }, 

        Err(e) => panic!("We don't expect it here {}!", e.get_ref()) // 'e' carries the guard inside it
    }; // <-- scope ends
    
    println!("stringified -> {:?}", stringified);
    println!("Mutex itself after guard is released -> {:?}", a_mutex);
    println!("Good bye!");
}
```
The output confirms that the lock is released, but after the entire *match* expression, ends:

```bash
Mutex itself while guard is alive, on a match arm -> Mutex { data: <locked>, poisoned: false, .. }
stringified -> "5"
Mutex itself after guard is released -> Mutex { data: 5, poisoned: false, .. }
Good bye!
```

So, the temporary here is being held inside the arm. Let us go a bit further.

In this case:

```rust
 use std::sync::Mutex;
fn main() {
    let a_mutex = Mutex::new(5);

    let stringified = a_mutex.lock().unwrap().to_string(); // <-- MutexGuard's scope ends heres
    println!("stringified -> {:?}", stringified);
    println!("Mutex itself after guard is released -> {:?}", a_mutex);
    println!("Good bye!");
}
```

The lock is released at the end of the statement ( conceptually, when **;** is reached ).

But, not in this case:

```rust
 use std::sync::Mutex;
fn main() {

    let a_mutex = Mutex::new(5);
    let stringified = match a_mutex.lock().unwrap().to_string().as_str() {

        s @ "5" => {
            println!("We have received a five!");
            println!("Mutex itself while guard is alive, on a match arm -> {:?}", a_mutex);
            s.to_owned()
        },
        _ => "Not a five".to_owned()
    };  // <-- lock is released here!
    
    println!("stringified -> {:?}", stringified);
    println!("Mutex itself after guard is released -> {:?}", a_mutex);
}
```

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

**Note**: That `.as_str()` is required for idiomatically *match*ing String literals. It has got nothing to do with the core discussion here. The basic point remains the same: how long is the lock being acquired and held?

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

The scoping rules come into play. The expression that follows a `match` is called a **[Scrutinee Expression](https://rustwiki.org/en/reference/glossary.html#scrutinee)**. In case a scrutinee expression generates a temporary value (viz., `MutexGuard`) it is not dropped till the end of scope of `match` expression. As a consequence, the lock is held, till the `match` expression comes to an end.

This has implications which may not be obvious. For example, what happens in this case?

```rust
use std::sync::Mutex;
fn main() {

    let a_mutex = Mutex::new(5);
    let stringified = match a_mutex.lock().unwrap().to_string().as_str() {

        s @ "5" => {
            println!("Mutex itself while guard is alive, on a match arm -> {:?}", a_mutex);
            // the lock is held here..
            let p = do_some_long_calculation(); // Example function, not implemented
            // the lock is still held here..
            let q = make_a_network_call(&p);    // Example function, not implemented
            s.to_owned() + q
        },
        _ => "Not a five".to_owned()
    };  // <-- lock is released here!
    
    println!("stringified -> {:?}", stringified);
    println!("Mutex itself after guard is released -> {:?}", a_mutex);
}
```
Executing functions, which take time to finish, will effectively prevent the lock from being released. If someone else is waiting for the lock (may be, another thread), tough luck!

Before we close, let's contrast the code above, with the code below:

```rust
use std::sync::Mutex;
fn main() {
    let a_mutex = Mutex::new(5);

    let unfenced = a_mutex.lock().unwrap().to_string(); // <-- MutexGuard goes out of scope

    let stringified = match unfenced.as_str() { // like earlier, ignore the as_str() function

        s @ "5" => {
            println!("We have received a five!");
            println!("Mutex itself while guard is alive, on a match arm -> {:?}", a_mutex);
            s.to_owned()
        },
        _ => "Not a five".to_owned()
    };
    
    println!("stringified -> {:?}", stringified);
    println!("Mutex itself after guard is released -> {:?}", a_mutex);
    println!("Good bye!");
}
```

### Main Takeaways

*   In order to extend a temporary value's scope, it is bound to a variable using the `let` statement.
*   In case of `MutexGuard`, a `let` statement forces the guard to exist for longer (depends on the scope of the `let` variable). While the guard exists, the lock on the innter piece of data remains acquired. 
*   In case using `match` expression that works on a temporary, the scope extends till the end of the `match` expression.
*   Because an arm of a `match` can execute arbitrary logic, the lock can be held for longer than expected or even, necessary. This is an important observation.

## Acknowledgements

In my quest to understand the behaviour of the compiler in this case, I have gone through a number of articles / blogs / explanations. I will be unfair and utterly discourteous on my part, if I don't mention the some of them. This blog stands on the shoulders of those authors and elaborators:

* My specific question on stackoverflow and fantastic answers given by [Chayim Friedman](https://stackoverflow.com/users/7884305/chayim-friedman), [masklinn](https://stackoverflow.com/users/8182118/masklinn), and [paul](https://stackoverflow.com/users/5209502/paul).
* This [stackoverflow](https://stackoverflow.com/questions/71170147/temporary-value-dropped-while-borrowed) QnA On, how the lock should be treated, in order to make use of the value tucked inside the mutex. 
* This [post]([https://](https://stackoverflow.com/questions/51335679/where-is-a-mutexguard-if-i-never-assign-it-to-a-variable)) has been extremely valuable for me to understand the issue of interplay between **Place Context** and **Value Context**! Thank you [Lukas Kalberbolt](https://stackoverflow.com/users/2408867/lukas-kalbertodt). A great question is posed by [AnimatedRNG](https://stackoverflow.com/users/498680/animatedrng).