Interestingly, the Rust language has very few concurrency features. Almost every concurrency feature we’ve talked about so far in this chapter has been part of the standard library, not the language. Your options for handling concurrency are not limited to the language or the standard library; you can write your own concurrency features or use those written by others.
However, two concurrency concepts are embedded in the language: the
Send marker trait indicates that ownership of values of the type
Send can be transferred between threads. Almost every Rust type
Send, but there are some exceptions, including
Rc<T>: this cannot be
Send because if you cloned an
Rc<T> value and tried to transfer ownership
of the clone to another thread, both threads might update the reference count
at the same time. For this reason,
Rc<T> is implemented for use in
single-threaded situations where you don’t want to pay the thread-safe
Therefore, Rust’s type system and trait bounds ensure that you can never
accidentally send an
Rc<T> value across threads unsafely. When we tried to do
this in Listing 16-14, we got the error
the trait Send is not implemented for Rc<Mutex<i32>>. When we switched to
Arc<T>, which is
Send, the code
Any type composed entirely of
Send types is automatically marked as
well. Almost all primitive types are
Send, aside from raw pointers, which
we’ll discuss in Chapter 19.
Sync marker trait indicates that it is safe for the type implementing
Sync to be referenced from multiple threads. In other words, any type
&T (an immutable reference to
Send, meaning the reference
can be sent safely to another thread. Similar to
Send, primitive types are
Sync, and types composed entirely of types that are
Sync are also
Sync is the most similar concept in Rust to the colloquial meaning of the phrase "thread-safe", i.e. that a particular piece of data can be safely used by multiple concurrent threads. The reason for having separate
Sync traits is that a type can sometimes be one, or both, or neither. For example:
- The smart pointer
Rc<T>is also neither
Sync, for reasons described above.
RefCell<T>type (which we talked about in Chapter 15) and the family of related
T: Send), but they are not
RefCellcan be sent across a thread boundary, but not accessed concurrently because the implementation of borrow checking that
RefCell<T>does at runtime is not thread-safe.
- The smart pointer
Sync, and can be used to share access with multiple threads as you saw in the “Sharing a
Mutex<T>Between Multiple Threads” section.
- The type
MutexGuard<'a, T>that is returned by
T: Sync) but not
Send. It is specifically not
Sendbecause some platforms mandate that mutexes are unlocked by the same thread that locked them.
Because types that are made up of
Sync traits are automatically
Sync, we don’t have to implement those traits manually. As
marker traits, they don’t even have any methods to implement. They’re just
useful for enforcing invariants related to concurrency.
Manually implementing these traits involves implementing unsafe Rust code.
We’ll talk about using unsafe Rust code in Chapter 19; for now, the important
information is that building new concurrent types not made up of
Sync parts requires careful thought to uphold the safety guarantees. “The
Rustonomicon” has more information about these guarantees and how to
This isn’t the last you’ll see of concurrency in this book: the project in Chapter 20 will use the concepts in this chapter in a more realistic situation than the smaller examples discussed here.
As mentioned earlier, because very little of how Rust handles concurrency is part of the language, many concurrency solutions are implemented as crates. These evolve more quickly than the standard library, so be sure to search online for the current, state-of-the-art crates to use in multithreaded situations.
The Rust standard library provides channels for message passing and smart
pointer types, such as
Arc<T>, that are safe to use in
concurrent contexts. The type system and the borrow checker ensure that the
code using these solutions won’t end up with data races or invalid references.
Once you get your code to compile, you can rest assured that it will happily
run on multiple threads without the kinds of hard-to-track-down bugs common in
other languages. Concurrent programming is no longer a concept to be afraid of:
go forth and make your programs concurrent, fearlessly!
Next, we’ll talk about idiomatic ways to model problems and structure solutions as your Rust programs get bigger. In addition, we’ll discuss how Rust’s idioms relate to those you might be familiar with from object-oriented programming.