Extensible Concurrency: Sync and Send (original) (raw)

  1. Foreword
  2. Introduction
  3. 1. Getting Started
    1. 1.1. Installation
    2. 1.2. Hello, World!
    3. 1.3. Hello, Cargo!
  4. 2. Programming a Guessing Game
  5. 3. Common Programming Concepts
    1. 3.1. Variables and Mutability
    2. 3.2. Data Types
    3. 3.3. How Functions Work
    4. 3.4. Comments
    5. 3.5. Control Flow
  6. 4. Understanding Ownership
    1. 4.1. What is Ownership?
    2. 4.2. References & Borrowing
    3. 4.3. Slices
  7. 5. Using Structs to Structure Related Data
    1. 5.1. Defining and Instantiating Structs
  8. 5.2. An Example Program Using Structs
  9. 5.3. Method Syntax
  10. 6. Enums and Pattern Matching
    1. 6.1. Defining an Enum
  11. 6.2. The match Control Flow Operator
  12. 6.3. Concise Control Flow with if let
  13. 7. Packages, Crates, and Modules
    1. 7.1. Packages and crates for making libraries and executables
  14. 7.2. Modules and use to control scope and privacy
  15. 8. Common Collections
    1. 8.1. Vectors
  16. 8.2. Strings
  17. 8.3. Hash Maps
  18. 9. Error Handling
    1. 9.1. Unrecoverable Errors with panic!
  19. 9.2. Recoverable Errors with Result
  20. 9.3. To panic! or Not To panic!
  21. 10. Generic Types, Traits, and Lifetimes
    1. 10.1. Generic Data Types
  22. 10.2. Traits: Defining Shared Behavior
  23. 10.3. Validating References with Lifetimes
  24. 11. Testing
    1. 11.1. Writing tests
  25. 11.2. Running tests
  26. 11.3. Test Organization
  27. 12. An I/O Project: Building a Command Line Program
    1. 12.1. Accepting Command Line Arguments
  28. 12.2. Reading a File
  29. 12.3. Refactoring to Improve Modularity and Error Handling
  30. 12.4. Developing the Library’s Functionality with Test Driven Development
  31. 12.5. Working with Environment Variables
  32. 12.6. Writing Error Messages to Standard Error Instead of Standard Output
  33. 13. Functional Language Features: Iterators and Closures
    1. 13.1. Closures: Anonymous Functions that Can Capture Their Environment
  34. 13.2. Processing a Series of Items with Iterators
  35. 13.3. Improving Our I/O Project
  36. 13.4. Comparing Performance: Loops vs. Iterators
  37. 14. More about Cargo and Crates.io
    1. 14.1. Customizing Builds with Release Profiles
  38. 14.2. Publishing a Crate to Crates.io
  39. 14.3. Cargo Workspaces
  40. 14.4. Installing Binaries from Crates.io with cargo install
  41. 14.5. Extending Cargo with Custom Commands
  42. 15. Smart Pointers
    1. 15.1. Box Points to Data on the Heap and Has a Known Size
  43. 15.2. The Deref Trait Allows Access to the Data Through a Reference
  44. 15.3. The Drop Trait Runs Code on Cleanup
  45. 15.4. Rc, the Reference Counted Smart Pointer
  46. 15.5. RefCell and the Interior Mutability Pattern
  47. 15.6. Creating Reference Cycles and Leaking Memory is Safe
  48. 16. Fearless Concurrency
    1. 16.1. Threads
  49. 16.2. Message Passing
  50. 16.3. Shared State
  51. 16.4. Extensible Concurrency: Sync and Send
  52. 17. Object Oriented Programming Features of Rust
    1. 17.1. Characteristics of Object-Oriented Languages
  53. 17.2. Using Trait Objects that Allow for Values of Different Types
  54. 17.3. Implementing an Object-Oriented Design Pattern
  55. 18. Patterns Match the Structure of Values
    1. 18.1. All the Places Patterns May be Used
  56. 18.2. Refutability: Whether a Pattern Might Fail to Match
  57. 18.3. All the Pattern Syntax
  58. 19. Advanced Features
    1. 19.1. Unsafe Rust
  59. 19.2. Advanced Lifetimes
  60. 19.3. Advanced Traits
  61. 19.4. Advanced Types
  62. 19.5. Advanced Functions & Closures
  63. 19.6. Macros
  64. 20. Final Project: Building a Multithreaded Web Server
    1. 20.1. A Single Threaded Web Server
  65. 20.2. Turning our Single Threaded Server into a Multithreaded Server
  66. 20.3. Graceful Shutdown and Cleanup
  67. 21. Appendix
    1. 21.1. A - Keywords
  68. 21.2. B - Operators and Symbols
  69. 21.3. C - Derivable Traits
  70. 21.4. D - Useful Development Tools
  71. 21.5. E - Editions
  72. 21.6. F - Translations
  73. 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.

Summary

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.