&str
(there’s more than one)&mut
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!
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:
String literals—You make these when you write let my_str = "I am a &str";
. They last for the whole program because they are written directly into the binary. They have the type &'static str
. The '
means its lifetime, and string literals have a lifetime called static
.
Borrowed str
—This is the regular &str
form without a 'static
lifetime. If you have a String
and pass a reference to it (a &String
), Rust will convert it to a &str
when you need it. This is thanks to a trait called Deref
. We will learn to use Deref
in chapter 15, but, for the moment, just remember that you can pass in a &String
to a function that takes a &str
.
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.
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.
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.
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,
};
}
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.
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); }
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
.”
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:
You can stay with owned types, use clones, etc., if you want to avoid lifetimes for the time being. If you get a &'a str
in a function, you can just turn it into a String
and put that on your struct! Rust is extremely fast, even when you do this.
Much of the time, when the compiler wants a lifetime, you will just end up writing <'a>
in a few places, and then it will work. It’s just a way of saying, “Don’t worry, I won’t give you anything that doesn’t live long enough.”
You can explore lifetimes just a bit at a time. Write some code with owned values and then make one a reference. The compiler will start to complain but also give some suggestions. If it gets too complicated, you can undo it and try again next time.
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) } }
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(); }
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.
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.
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
.
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(); ② }
② 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
:
Always immediately change the value with .borrow_mut()
without assigning this to a variable. If no variables are holding on to the output of .borrow_mut()
, there is no way the code will panic.
Use the .try_borrow_mut()
method instead of borrow_mut()
if there is a chance of a double borrow. This will return an error if the RefCell
is already borrowed.
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!
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.
⑥ mutex_changer is a MutexGuard<i32>, but we want to change the i32 itself. We can use * to change the i32 (the inner value).
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.
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 Mutex
es 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; } }
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:
You can’t hold anything else on top of a variable returned from .write().
You can’t have an extra variable made with .write()
or even with .read()
.
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(); ③ }
③ 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.
If you don’t want to think too much about lifetime annotations yet, you can mostly avoid them by using owned data as much as possible.
A lifetime annotation is another type of generic annotation. They tell the compiler what lifetimes to expect, but they don’t change how long references live.
If you are using a &str
in one of your types but will only give it string literals, you can avoid lifetime annotations by having it take a &'static str
instead of a &'a str
.
If you need mutability but can’t or don’t want to use a &mut
, try one of the four interior mutability types. Cell
is best for Copy
types, RefCell
is similar to regular references, and Mutex
and RwLock
can be passed between threads.
When changing values inside a RefCell, Mutex
, and RwLock
, it’s easiest to change the values outright without making a variable that holds a borrow or a lock. Then, you won’t have to think about whether the variable that holds the borrow or lock is dropped or not.
If you need to use variables that hold a borrow or a lock, you can use methods like .try_borrow()
and .try_lock()
to make sure that there won’t be a deadlock.