10 Lifetimes and interior mutability

This chapter covers

It is now time to learn about Rust’s famous lifetimes, used by the compiler to know when variables can be dropped and how long references last. Usually, you don’t need to specify lifetimes in your code, but sometimes the compiler needs a bit of help and will ask you to tell it how long something should last. We are also going to learn how to (safely!) mutate values without needing a mutable reference to do it!

10.1 Types of &str

We’ve been using &str for most of the book so far. But here’s an interesting fact about them: there is actually more than one type of &str. The two ways you’ll see a &str are as follows:

Here is an example of a borrowed str:

fn prints_str(my_str: &str) { 
    println!("{my_str}");
}
 
fn main() {
    let my_string = String::from("I am a string");
    prints_str(&my_string);
}

We know that you can’t return a reference from something that only lives inside a function because it dies as soon as the function is over. When the variable dies, you don’t want to have a reference pointing to where the data was. That’s unsafe, so Rust doesn’t allow it. But when using a str with a 'static lifetime, the data never disappears. So, you can return a reference to it! In the following code, the first function will work, but the second will not:

fn works() -> &'static str {
    "I live forever!"
}
 
// fn does_not_work() -> &'static str {
//    &String::from("Sorry, I only live inside the fn. Not 'static")
// }

You are probably getting the feeling that lifetimes are a pretty big subject in Rust. They definitely are. Let’s start learning how they work.

10.2 Lifetime annotations

We already learned that a lifetime means “how long the variable or reference lives.” Most of the time, Rust takes care of lifetimes for you, but sometimes, it needs a bit of extra help. This extra help is called a lifetime annotation, which means “extra lifetime information.” You only need to think about lifetimes with references. References aren’t allowed to live longer than the object they come from because references point to the same memory, and this memory gets freed up when the object is gone. It would be a big problem if references could live longer because then they could point to memory that is already cleaned up and used by something else. You see lifetime annotations in a lot of places. We’ll start with lifetime annotations in functions.

10.2.1 Lifetimes in functions

Lifetimes are not too hard to work with in functions because functions have a nice clear start and end. Here’s an example of a function that doesn’t work:

fn returns_reference() -> &str {
    let my_string = String::from("I am a string");
    &my_string
}

The problem is that my_string only lives inside returns_reference. We try to return &my_string, but &my_string can’t exist without my_string. So the compiler says no.

Writing the code this way doesn’t fix the problem, either:

fn returns_str() -> &str {
    let my_string = String::from("I am a string");
    "I am a str"
}
 
fn main() {
    let my_str = returns_str();
    println!("{my_str}");
}

In both cases, it almost works. Each time we try, the compiler says

error[E0106]: missing lifetime specifier
 --> src\main.rs:6:21
  |
6 | fn returns_str() -> &str {
  |                     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there
  is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
6 | fn returns_str() -> &'static str {
  |                     ^^^^^^^^

This missing lifetime specifier means that we need to add a ' to the lifetime. The next part of the error message says that it contains a borrowed value, but there is no value for it to be borrowed from. This message means that the return value for the function is &str, which is a borrowed str, but I am a str isn’t borrowed from a variable. However, the compiler guesses at what we are trying to do by suggesting consider using the 'static lifetime by writing &'static str, which is a string literal.

If we try the compiler’s suggestion, the code will now compile:

fn returns_str() -> &'static str {
    let my_string = String::from("I am a string");
    "I am a str"
}
 
fn main() {
    let my_str = returns_str();
    println!("{my_str}");
}

Of course, the code only worked because we were outright ignoring my_string and letting it die inside the function. But you can see that the compiler is satisfied that we returned a &str with a lifetime of 'static. Meanwhile, my_string can only be returned as an owned String: we can’t return a reference to it because it is going to die after the next line.

Now, fn returns_str() -> &'static str tells Rust: “Don’t worry; we will only return a string literal.” String literals live for the whole program, so the compiler is now happy with this.

You might notice that lifetime annotations work in a similar way to generic annotations. When we tell the compiler something like <T: Display>, we promise that we will only use something that implements Display. The compiler will understand this and reject anything that doesn’t implement Display. And when we tell the compiler that a function returns a &'static str, it will understand and reject anything that doesn’t have this lifetime. But writing &'static str doesn’t give anything a 'static lifetime in the same way that writing T: Display doesn’t give anything the trait Display.

However, 'static is not the only lifetime: every variable has a lifetime, but we don’t usually have to write it. The compiler is pretty smart and can usually figure it out for itself. We only have to write the lifetime for references when the compiler can’t decide on its own.

10.2.2 Lifetime annotations in types

Here is an example of another lifetime. Imagine we want to create a City struct and try to give it a &str for the name instead of a String. Interestingly, if we write &str instead of String, the code won’t compile:

#[derive(Debug)]
struct City {
    name: &str,           
    date_founded: u32,
}
 
fn main() {
    let my_city = City {
        name: "Ichinomiya",
        date_founded: 1921,
    };
}

Here’s the problem.

The compiler says:

error[E0106]: missing lifetime specifier
 --> src\main.rs:3:11
  |
3 |     name: &str,
  |           ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
2 | struct City<'a> {
3 |     name: &'a str,
  |

Rust needs a lifetime for &str because &str is a reference. What happens when the value that name points to is dropped? Its memory would be cleaned up, and the reference would point to nothing or even someone else’s data. That would be unsafe, so Rust doesn’t allow it.

What about 'static? Will that work? We used it before. Let’s try:

#[derive(Debug)]
struct City {
    name: &'static str,     
    date_founded: u32,
}
 
fn main() {
    let my_city = City {
        name: "Ichinomiya",
        date_founded: 1921,
    };
 
    println!("{} was founded in {}", my_city.name, my_city.date_founded);
}

Changes &str to &'static str

Okay, that works. Maybe this is what you wanted for the struct. However, note that now we can only take string literals, not references to something else. That’s because we told the compiler that we would only give it something that can live for the whole life of the program. So, this will not work:

#[derive(Debug)]
struct City {
    name: &'static str,                              
    date_founded: u32,
}
 
fn main() {
    let city_names = vec!["Ichinomiya".to_string(), 
    "Kurume".to_string()];                           
 
    let my_city = City { 
        name: &city_names[0],                        
        date_founded: 1921,
    };
 
    println!("{} was founded in {}", my_city.name, my_city.date_founded);
}

The parameter name is 'static, so it must be able to live for the whole program.

However, city_names does not live for the whole program.

This is a &str, not a &'static str. It is a reference to a value inside city_names.

The compiler says:

error[E0597]: `city_names` does not live long enough
  --> src\main.rs:12:16
   |
12 |         name: &city_names[0],
   |                ^^^^^^^^^^
   |                |
   |                borrowed value does not live long enough
   |                requires that `city_names` is borrowed for `'static`
...
18 | }
   | - `city_names` dropped here while still borrowed

This is important to understand because the reference we gave it does live long enough for us to print the struct City. But we promised that we would only give it a &'static str, and that is what it expects.

Now, we will try what the compiler suggested before. It said to try writing struct City<'a> and name: &'a str. This means that it will only take a reference for name if it lives as long as City.

You can read the <'a> and name: &'a str in the code as “The City struct has a lifetime that we will call 'a, and its name property must also live at least as long as 'a. Other shorter lifetimes will not be accepted.”

#[derive(Debug)]
struct City<'a> {        
    name: &'a str,       
    date_founded: u32,
}
 
fn main() {
    let city_names = vec!["Ichinomiya".to_string(), "Kurume".to_string()];
 
    let my_city = City {
        name: &city_names[0],
        date_founded: 1921,
    };
 
    println!("{} was founded in {}", my_city.name, my_city.date_founded);
}

City has lifetime 'a.

Name also has lifetime 'a.

Also, remember that you can write anything instead of 'a if you want. This is also similar to generics, where we write T and U but can write anything.

#[derive(Debug)]
struct City<'city> {       
    name: &'city str,      
    date_founded: u32,
}

The lifetime is now called 'city.

Name has the 'city lifetime.

Usually, you will write 'a, 'b, 'c, etc., because it is quick and the usual way to write. But you can change it if you want. One good tip is that changing the lifetime to a human-readable name can help you read code if it is very complicated.

Let’s look at the comparison to traits for generics again with this example:

use std::fmt::Display;
 
fn prints<T: Display>(input: T) {
    println!("T is {input}");
}

When you write T: Display, it means “Please only take T if it has the trait Display.” It does not mean “I am giving the trait Display to T.”

The same is true for lifetimes. Take a close look at 'a here:

#[derive(Debug)]
struct City<'a> {
    name: &'a str,
    date_founded: u32,
}

The 'a means “Please only take an input for name if it lives at least as long as City.” It does not mean, “This will make the input for name live as long as City.”

10.2.3 The anonymous lifetime

Do you remember seeing a <'_> back in chapter 7 when we implemented Display for our Cat struct? Here’s what we wrote:

impl fmt::Display for Cat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} is a cat who is {} years old.", self.name, self.age)
    }
}

Now, we can finally learn what this <'_> means. This is called the anonymous lifetime and is an indicator that references are being used. Rust will suggest it to you when you are implementing structs, for example. Here is one struct that almost works but not yet:

struct Adventurer<'a> {
    name: &'a str,
    hit_points: u32,
}
 
impl Adventurer {
    fn take_damage(&mut self) {
        self.hit_points -= 20;
        println!("{} has {} hit points left!", self.name, self.hit_points);
    }
}

We did what we needed to do for the struct: we said that name comes from a &str. That means we need to indicate a lifetime, so we gave it <'a>. But then Rust tells us to indicate a lifetime again inside the impl block:

error[E0726]: implicit elided lifetime not allowed here
 --> src\main.rs:6:6
  |
6 | impl Adventurer {
  |      ^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`

It wants us to add that anonymous lifetime to show that there is a reference being used. If we write that, it will be happy:

struct Adventurer<'a> {
    name: &'a str,
    hit_points: u32,
}
 
impl Adventurer<'_> {
    fn take_damage(&mut self) {
        self.hit_points -= 20;
        println!("{} has {} hit points left!", self.name, self.hit_points);
    }
}

This lifetime was made so that you don’t always have to write things like impl<'a> Adventurer<'a>, because the struct already shows the lifetime.

Hold on, though. Why does the impl block need to talk about lifetimes, too? Let’s pretend there is a trait that needs to deal with two lifetimes. It might look like this:

trait HasSomeLifeTime<'a, 'b> {}

You might also have a struct that also has two references, and each one has its own lifetime for some reason. (Don’t worry, this isn’t something you usually see in Rust. It’s just to explain.)

struct SomeStruct<'a, 'b> {
    name: &'a str,
    other: &'b str
}

Imagine you want to implement HasSomeLifeTime for SomeStruct. The trait has its own lifetimes to deal with, and the struct has its own lifetimes to deal with. Both the struct and the trait choose to call them 'a and 'b, but 'a and 'b in the struct SomeStruct have nothing to do with 'a and 'b in the trait HasSomeLifeTime. Therefore, when you use impl, you declare some lifetimes, and that’s when you can decide how long one lifetime must be compared to the other.

You might implement the trait like this:

impl <'a, 'b> HasSomeLifeTime<'a, 'b> for SomeStruct<'a, 'b> {}

This means “We are talking about two different lifetimes here, 'a and 'b.” Now, the 'a and 'b for the trait and the struct are the same lifetime.

But maybe you don’t want to say that they will all be the same, and maybe you don’t want to use the same names either. You could even write this:

impl <'one, 'two, 'three, 'four> HasSomeLifeTime<'one, 'three> for
SomeStruct<'two, 'four> {}

This means “There are four lifetimes involved here,” and the trait has its own two while the struct has its own two. The four lifetimes can now all be separate from each other.

But you almost never need to worry about lifetimes to this point in Rust, so don’t worry. Even in this complex example, you can just elide the lifetimes and let Rust figure it out:

impl HasSomeLifeTime<'_, '_> for SomeStruct<'_, '_> {}

That means “Each one has its own two lifetimes; you figure it out.” To emphasize this point again: it is very rare to deal with this many lifetimes in Rust!

Lifetimes can be difficult in Rust, but here are some tips to avoid getting too stressed about them:

Let’s do this with our code and see what the compiler says. We’ll go back and take the lifetimes out and implement Display. Display will just print the Adventurer’s name. Here is the code again that won’t compile:

struct Adventurer {
    name: &str,
    hit_points: u32,
}
 
impl Adventurer {
    fn take_damage(&mut self) {
        self.hit_points -= 20;
        println!("{} has {} hit points left!", self.name, self.hit_points);
    }
}
 
impl std::fmt::Display for Adventurer {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{} has {} hit points.", self.name, self.hit_points)
        }
}

The first complaint is this:

error[E0106]: missing lifetime specifier
 --> src\main.rs:2:11
  |
2 |     name: &str,
  |           ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 | struct Adventurer<'a> {
2 |     name: &'a str,
  |

It suggests what to do: <'a> after Adventurer and &'a str. So we do that. The code is closer to compiling but not quite:

struct Adventurer<'a> {
    name: &'a str,
    hit_points: u32,
}
 
impl Adventurer {
    fn take_damage(&mut self) {
        self.hit_points -= 20;
        println!("{} has {} hit points left!", self.name, self.hit_points);
    }
}
 
impl std::fmt::Display for Adventurer {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{} has {} hit points.", self.name, self.hit_points)
        }
}

The compiler is now happy with our changes, but it is wondering about the impl blocks. It wants us to mention that it’s using references:

error[E0726]: implicit elided lifetime not allowed here
 --> src\main.rs:6:6
  |
6 | impl Adventurer {
  |      ^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`
 
error[E0726]: implicit elided lifetime not allowed here
  --> src\main.rs:12:28
   |
12 | impl std::fmt::Display for Adventurer {
   |                            ^^^^^^^^^^- help: indicate the anonymous
                                lifetime: `<'_>`

Okay, so we add the anonymous lifetime as it suggests, and now it works! Now, we can make an Adventurer and do some things with it:

struct Adventurer<'a> {
    name: &'a str,
    hit_points: u32,
}
 
impl Adventurer<'_> {
    fn take_damage(&mut self) {
        self.hit_points -= 20;
        println!("{} has {} hit points left!", self.name, self.hit_points);
    }
}
 
impl std::fmt::Display for Adventurer<'_> {
 
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{} has {} hit points.", self.name, self.hit_points)
        }
}
 
fn main() {
    let mut billy = Adventurer {
        name: "Billy",
        hit_points: 100_000,
    };
    println!("{}", billy);
    billy.take_damage();
}

This prints

Billy has 100000 hit points.
Billy has 99980 hit points left!

You can see that lifetimes are often the compiler wanting to make sure. It is usually smart enough to almost guess at what lifetimes you want and just needs you to tell it so that it can be certain.

10.3 Interior mutability

Interior mutability means having a little bit of mutability on the inside (the interior). Remember, in Rust, you need to use mut to change a variable. But there are also some ways to change them without the word mut. This is because Rust has some ways to let you safely change values inside of a struct that is itself immutable. Each way of doing so follows some rules that make sure that changing the values is still safe.

Let’s look at a simple example where we would want this. Imagine a struct called PhoneModel with many fields:

struct PhoneModel {
    company_name: String,
    model_name: String,
    screen_size: f32,
    memory: usize,
    date_issued: u32,
    on_sale: bool,
}
 
impl PhoneModel {             
    fn method_one(&self) {}
    fn method_two(&self) {}
}
 
fn main() {
    let super_phone_3000 = PhoneModel {
        company_name: "YY Electronics".to_string(),
        model_name: "Super Phone 3000".to_string(),
        screen_size: 7.5,
        memory: 4_000_000,
        date_issued: 2020,
        on_sale: true,
    };
 
}

These methods are unfinished so they don’t do anything, but imagine we have a lot of methods that all take &self but that we’d like to mutate some data inside PhoneModel.

Maybe we want the fields in PhoneModel to be immutable because we don’t want the data to change. The date_issued and screen_size never change, for example, and the methods we have for PhoneModel use &self and not a &mut self, which is more convenient for us. We’d rather not have to use &mut self if we don’t have to.

But inside is one field called on_sale. A phone model will first be on sale (true), but later, the company will stop selling it. Can we make just this one field mutable? We don’t want to write let mut super_phone_3000;. If we do, the whole struct will become mutable. Maybe there is a function we need to use that takes a &PhoneModel as an input, not a &mut Phonemodel, but we’d still like to mutate some data inside it.

Fortunately, there is a way to do this. Rust has four main ways to allow some safe mutability inside of something that is immutable: Cell, RefCell, Mutex, and RwLock. Let’s look at them now.

10.3.1 Cell

The simplest way to use interior mutability in Rust is called Cell, which its documentation describes as a “mutable memory location.” The signature of Cell is just Cell<T>, the T being the data type you want it to hold. Let’s use Cell for our previous PhoneModel.

First, we write use std::cell::Cell;, so we can just write Cell instead of std::cell::Cell every time. Then we change on_sale: bool to on_sale: Cell<bool>. Now, it isn’t a bool: it’s a Cell that holds a bool.

Cell has a method called .set() where you can change the value. We use .set() to change on_sale: true to on_sale: Cell::new(false):

use std::cell::Cell;
 
#[derive(Debug)]
struct PhoneModel {
    company_name: String,
    model_name: String,
    screen_size: f32,
    memory: usize,
    date_issued: u32,
    on_sale: Cell<bool>,
}
 
impl PhoneModel {    
    fn make_not_on_sale(&self) {
        self.on_sale.set(false);
    }
}
 
fn main() {
    let super_phone_3000 = PhoneModel {
        company_name: "YY Electronics".to_string(),
        model_name: "Super Phone 3000".to_string(),
        screen_size: 7.5,
        memory: 4_000_000,
        date_issued: 2020,
        on_sale: Cell::new(true),
    };

Ten years later, super_phone_3000 is not on sale anymore:

    super_phone_3000.make_not_on_sale();
    println!("{super_phone_3000:#?}");
}

The input shows that the value for on_sale has changed to false without us needing to use any mutable references:

PhoneModel {
    company_name: "YY Electronics",
    model_name: "Super Phone 3000",
    screen_size: 7.5,
    memory: 4000000,
    date_issued: 2020,
    on_sale: Cell {
        value: false,
    },
}

Cell works for all types, but it works best for simple Copy types because it gives values, not references. Cell has a method called .get(), for example, that only works when the inner type implements Copy.

Another type that you can use is RefCell.

10.3.2 RefCell

A RefCell is another way to change values without needing to declare mut. It means “reference cell” and is a bit similar not only to a Cell but also to regular references.

Let’s make a User struct for the next example that holds a RefCell. So far, you can see that it is similar to Cell in that it holds a value, and you use a method called new() to create it:

use std::cell::RefCell;
 
#[derive(Debug)]
struct User {
    id: u32,
    year_registered: u32,
    username: String,
    active: RefCell<bool>,     
}
 
fn main() {
    let user_1 = User {
        id: 1,
        year_registered: 2020,
        username: "User 1".to_string(),
        active: RefCell::new(true),
    };
 
    println!("{:?}", user_1.active);
}

In real life, this would have a lot more fields, but we’ll just include a few to keep it short.

This prints RefCell { value: true }.

There are many methods for RefCell. Two of them are .borrow() and .borrow_mut(). With these methods, you can do the same thing you do with & and &mut. The rules are the same:

Changing the value in a RefCell feels pretty much the same as using a mutable reference. You can create a variable that can be used to mutate the value and then change the value that way:

    let user_1 = User {
        id: 1,
        year_registered: 2020,
        username: "User 1".to_string(),
        active: RefCell::new(true),
    };
    let mut borrow = user_1.active.borrow_mut();
    *borrow = false;

Note how similar this is to using a mutable reference without a RefCell if user_1 itself had been declared as mut:

    let borrow = &mut user_1.active;
    *borrow = false;

Or you can change the value without declaring a variable to do it with:

    let user_1 = User {
        id: 1,
        year_registered: 2020,
        username: "User 1".to_string(),
        active: RefCell::new(true),
    };
    *user_1.active.borrow_mut() = false;

But you have to be careful with a RefCell because it checks borrows at run time, not compilation time. So the following will compile, even though it is wrong:

use std::cell::RefCell;
 
#[derive(Debug)]
struct User {
    id: u32,
    year_registered: u32,
    username: String,
    active: RefCell<bool>,  
}
 
fn main() {
    let user_1 = User {
        id: 1,
        year_registered: 2020,
        username: "User 1".to_string(),
        active: RefCell::new(true),
    };
 
    let borrow_one = user_1.active.borrow_mut();    
    let borrow_two = user_1.active.borrow_mut();    
}

First mutable borrow—okay

Second mutable borrow—not okay

But if you run it, it will immediately panic:

thread 'main' panicked at 'already borrowed: BorrowMutError',
src\main.rs:21:36
Note: run with `RUST_BACKTRACE=1` environment variable to display a
backtrace

There are two ways to be sure that your code won’t panic when using a RefCell:

So that was a quick introduction to Cell and RefCell, the two simplest types for interior mutability in Rust. The next two are known as Mutex and RwLock and will seem sort of similar to RefCell. So why do they exist? They exist because of multiple threads, which are used to do two things at once in your code. Cell and RefCell don’t have any guards in place to make sure that data isn’t being changed at the same time, so Rust won’t let you use them in multiple threads. Here is a quick teaser:

use std::cel::RefCell;
 
fn main() {
    let bool_in_refcell = RefCell::new(true);
 
    std::thread::spawn(|| {                      
        *bool_in_refcell.borrow_mut() = false;
    });
}

Here, we try to start a new thread to do something at the same time as the rest of the code. But Rust won’t let us use a RefCell inside it.

Rust tells us exactly why it won’t let the code compile:

error[E0277]: `RefCell<bool>` cannot be shared between threads safely

We will learn to use multiple threads in the next chapter, but, for now, just remember that this is why the next two types exist. So let’s move on to the next one!

10.3.3 Mutex

Mutex is another way to change values without declaring mut. Mutex means “mutual exclusion,” which means “only one at a time.” This is why a Mutex is safe because it only lets one thread change it at a time. To do this, it uses a method called .lock(), which returns a struct called a MutexGuard. This MutexGuard is like locking a door from the inside. You go into a room and lock the door, and now you can change things inside the room. Nobody else can come in and stop you because you locked the door.

A Mutex is easier to understand through examples. In this example, note that Mutex is located at std::sync::Mutex. Inside the standard library, sync is for types that are thread-safe, meaning they can be used in multiple threads:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);                       
 
    let mut mutex_changer = my_mutex.lock().unwrap();   
 
    println!("{my_mutex:?}");                           
 
    println!("{mutex_changer:?}");                      
 
    *mutex_changer = 6;   
 
     
    println!("{mutex_changer:?}");                      
}

A new Mutex<i32>. We don’t need to declare it as mut.

mutex_changer is a MutexGuard, which gives access to the Mutex. It has to be mut because we will change it. The Mutex itself is now locked.

Here we can see that the Mutex is locked as it prints "Mutex { data: <locked> }". The only way to access and change the data is through mutex_changer.

This prints 5. Let’s change it to 6.

And now it prints 6.

mutex_changer is a MutexGuard<i32>, but we want to change the i32 itself. We can use * to change the i32 (the inner value).

Here is the output:

Mutex { data: <locked>, poisoned: false, .. }
5
6

But mutex_changer still holds a lock after it is done changing the value. How do we stop it and unlock the Mutex? A Mutex is unlocked when the MutexGuard goes out of scope (when it is dropped). One way to do this is to put the MutexGuard into its own scope:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);
    {
        let mut mutex_changer = my_mutex.lock().unwrap();   
        *mutex_changer = 6;
    }
 
    println!("{my_mutex:?}");
}

At this point, mutex_changer goes out of scope and is now gone, and my_mutex isn’t locked anymore.

Note that the output now shows us the data inside because we print my_mutex when it isn’t locked anymore:

Mutex { data: 6, poisoned: false, .. }

There is an easier way to unlock a Mutex, though, thanks to a convenient function called drop(), which automatically makes an object go out of scope. We can simply stick mutex_changer inside drop(), and it will cease to exist:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);
    let mut mutex_changer = my_mutex.lock().unwrap();
    *mutex_changer = 6;
    drop(mutex_changer);         
 
    println!("{my_mutex:?}");    
}

This drops mutex_changer. It is now gone, and my_mutex is unlocked.

Output: Mutex { data: 6 }

You have to be careful with a Mutex because if another variable tries to .lock() it, it will wait forever. This is known as a deadlock:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);
    let mut mutex_changer = my_mutex.lock().unwrap();          
    let mut other_mutex_changer = my_mutex.lock().unwrap();    
 
    println!("This will never print...");
}

mutex_changer has the lock after this line.

But other_mutex_changer wants the lock, too. The program will wait forever.

This behavior makes sense because Mutexes are made for usage across multiple threads, and if you have two threads doing two things at the same time, any call to .lock() to a Mutex that is already locked should wait until the other thread is done. But it also means that you have to be a bit careful with your code to avoid deadlocks.

The solutions to this are similar to the solutions mentioned in section 10.3.2 on RefCell. Instead of .lock(), you can use a method called .try_lock(). This method will try once, and if it doesn’t get the lock, it will give up. You can use if let or match for this:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);
    let mut mutex_changer = my_mutex.lock().unwrap();
    let mut other_mutex_changer = my_mutex.try_lock();
 
    if let Ok(value) = other_mutex_changer {
        println!("The MutexGuard has: {value}")
    } else {
        println!("Didn't get the lock")
    }
}

This code will print Didn't get the lock instead of deadlocking and holding up the program.

Same as with a RefCell, you don’t need to make a variable to change the Mutex. You can just use .lock() to change the value right away:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);
    *my_mutex.lock().unwrap() = 6;
}

When you type *my_mutex.lock().unwrap() = 6;, you never create a variable that holds the lock, so you don’t need to call drop(). You can do it 100 times if you want, and it doesn’t matter because no variable ever holds the lock:

use std::sync::Mutex;
 
fn main() {
    let my_mutex = Mutex::new(5);
    for _ in 0..100 {
        *my_mutex.lock().unwrap() += 1;
    }
}

10.3.4 RwLock

RwLock stands for “read–write lock.” It is not only like a Mutex because it is thread-safe, but it is also similar to a RefCell in the way it is used: you can get mutable or immutable references to the value inside. You use .write().unwrap() instead of .lock().unwrap() to change it. You can also use .read().unwrap() to get read access. RwLock is similar to RefCell because it follows the same rules that Rust uses for references:

But RwLock is also similar to Mutex in that the program will deadlock instead of panicking if you try to use .write() when you can’t get access:

use std::sync::RwLock;
 
fn main() {
    let my_rwlock = RwLock::new(5);
    let read1 = my_rwlock.read().unwrap();      
    let read2 = my_rwlock.read().unwrap();      
    println!("{read1:?}, {read2:?}");
    let write1 = my_rwlock.write().unwrap();    
}

One .read() is fine.

Another .read()—also fine

Uh oh, now we’re deadlocked.

This code will print 5, 5 and then deadlock forever.

To solve this, we can use drop() (or a new scope) just like in a Mutex:

use std::sync::RwLock;
 
fn main() {
    let my_rwlock = RwLock::new(5);
    let read1 = my_rwlock.read().unwrap();
    let read2 = my_rwlock.read().unwrap();
    println!("{read1:?}, {read2:?}");
    drop(read1);
    drop(read2);          
 
    let mut write1 = my_rwlock.write().unwrap();
    *write1 = 6;
    drop(write1);
    println!("{:?}", my_rwlock);
}

We dropped both, so we can use .write().

This time, there is no deadlock, and the output shows the changed value:

5, 5
RwLock { data: 6, poisoned: false, .. }

RwLock has the same .try_ methods as well to help ensure that you’ll never have a deadlock: .try_read() and .try_write:

fn main() {
    let my_rwlock = RwLock::new(5);
 
    let read1 = my_rwlock.read().unwrap();
    let read2 = my_rwlock.read().unwrap();
 
    if let Ok(mut number) = my_rwlock.try_write() {
        *number += 10;
        println!("Now the number is {}", number);
    } else {
        println!("Couldn't get write access, sorry!")
    };
}

Once again, the code gives up with the message Couldn't get write access, sorry! instead of deadlocking forever.

You learned in this chapter that sometimes the compiler doesn’t know, or can’t decide, how long a reference lives. In those rare cases, you have to tell it which lifetime to use. But the good news is that you can just use owned types in the meantime if you find lifetimes too annoying to use. You can always get used to lifetimes a bit at a time.

Also, hopefully, you enjoyed learning that Rust has some more flexibility than you thought. Rust is strict, but it’s not strict just to be strict. As long as there is a safe way to mutate a value, Rust has no problem with it.

The next chapter has a bunch of interesting stuff all together. You’ll learn how to use a Cow (yes, that’s a type in Rust) and reference counters and start learning about multiple threads to do many things at the same time.

Summary