Extensible Concurrency: Sync and Send (original) (raw)
- Foreword
- Introduction
- 1. Getting Started
- 2. Programming a Guessing Game
- 3. Common Programming Concepts
- 4. Understanding Ownership
- 5. Using Structs to Structure Related Data
- 5.2. An Example Program Using Structs
- 5.3. Method Syntax
- 6. Enums and Pattern Matching
- 6.2. The match Control Flow Operator
- 6.3. Concise Control Flow with if let
- 7. Packages, Crates, and Modules
- 7.2. Modules and use to control scope and privacy
- 8. Common Collections
- 8.2. Strings
- 8.3. Hash Maps
- 9. Error Handling
- 9.2. Recoverable Errors with Result
- 9.3. To panic! or Not To panic!
- 10. Generic Types, Traits, and Lifetimes
- 10.2. Traits: Defining Shared Behavior
- 10.3. Validating References with Lifetimes
- 11. Testing
- 11.2. Running tests
- 11.3. Test Organization
- 12. An I/O Project: Building a Command Line Program
- 12.2. Reading a File
- 12.3. Refactoring to Improve Modularity and Error Handling
- 12.4. Developing the Library’s Functionality with Test Driven Development
- 12.5. Working with Environment Variables
- 12.6. Writing Error Messages to Standard Error Instead of Standard Output
- 13. Functional Language Features: Iterators and Closures
- 13.2. Processing a Series of Items with Iterators
- 13.3. Improving Our I/O Project
- 13.4. Comparing Performance: Loops vs. Iterators
- 14. More about Cargo and Crates.io
- 14.2. Publishing a Crate to Crates.io
- 14.3. Cargo Workspaces
- 14.4. Installing Binaries from Crates.io with cargo install
- 14.5. Extending Cargo with Custom Commands
- 15. Smart Pointers
- 15.2. The Deref Trait Allows Access to the Data Through a Reference
- 15.3. The Drop Trait Runs Code on Cleanup
- 15.4. Rc, the Reference Counted Smart Pointer
- 15.5. RefCell and the Interior Mutability Pattern
- 15.6. Creating Reference Cycles and Leaking Memory is Safe
- 16. Fearless Concurrency
- 16.2. Message Passing
- 16.3. Shared State
- 16.4. Extensible Concurrency: Sync and Send
- 17. Object Oriented Programming Features of Rust
- 17.2. Using Trait Objects that Allow for Values of Different Types
- 17.3. Implementing an Object-Oriented Design Pattern
- 18. Patterns Match the Structure of Values
- 18.2. Refutability: Whether a Pattern Might Fail to Match
- 18.3. All the Pattern Syntax
- 19. Advanced Features
- 19.2. Advanced Lifetimes
- 19.3. Advanced Traits
- 19.4. Advanced Types
- 19.5. Advanced Functions & Closures
- 19.6. Macros
- 20. Final Project: Building a Multithreaded Web Server
- 20.2. Turning our Single Threaded Server into a Multithreaded Server
- 20.3. Graceful Shutdown and Cleanup
- 21. Appendix
- 21.2. B - Operators and Symbols
- 21.3. C - Derivable Traits
- 21.4. D - Useful Development Tools
- 21.5. E - Editions
- 21.6. F - Translations
- 21.7. G - How Rust is Made and “Nightly Rust”
The Rust Programming Language
Extensible Concurrency with the Sync and Send Traits
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: thestd::marker
traits Sync
and Send
.
Allowing Transference of Ownership Between Threads with Send
The Send
marker trait indicates that ownership of the type implementingSend
can be transferred between threads. Almost every Rust type is 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 performance penalty.
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 compiled.
Any type composed entirely of Send
types is automatically marked as Send
as well. Almost all primitive types are Send
, aside from raw pointers, which we’ll discuss in Chapter 19.
Allowing Access from Multiple Threads with Sync
The Sync
marker trait indicates that it is safe for the type implementingSync
to be referenced from multiple threads. In other words, any type T
isSync
if &T
(a reference to T
) is 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
.
The smart pointer Rc<T>
is also not Sync
for the same reasons that it’s notSend
. The RefCell<T>
type (which we talked about in Chapter 15) and the family of related Cell<T>
types are not Sync
. The implementation of borrow checking that RefCell<T>
does at runtime is not thread-safe. The smart pointer Mutex<T>
is Sync
and can be used to share access with multiple threads as you saw in the “Sharing a Mutex<T>
Between Multiple Threads” section.
Implementing Send and Sync Manually Is Unsafe
Because types that are made up of Send
and Sync
traits are automatically also Send
and 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 Send
andSync
parts requires careful thought to uphold the safety guarantees.The Rustonomicon has more information about these guarantees and how to uphold them.
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 Mutex<T>
and 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.