| # Send and Sync |
| |
| Not everything obeys inherited mutability, though. Some types allow you to |
| have multiple aliases of a location in memory while mutating it. Unless these types use |
| synchronization to manage this access, they are absolutely not thread-safe. Rust |
| captures this through the `Send` and `Sync` traits. |
| |
| * A type is Send if it is safe to send it to another thread. |
| * A type is Sync if it is safe to share between threads (T is Sync if and only if `&T` is Send). |
| |
| Send and Sync are fundamental to Rust's concurrency story. As such, a |
| substantial amount of special tooling exists to make them work right. First and |
| foremost, they're [unsafe traits]. This means that they are unsafe to |
| implement, and other unsafe code can assume that they are correctly |
| implemented. Since they're *marker traits* (they have no associated items like |
| methods), correctly implemented simply means that they have the intrinsic |
| properties an implementor should have. Incorrectly implementing Send or Sync can |
| cause Undefined Behavior. |
| |
| Send and Sync are also automatically derived traits. This means that, unlike |
| every other trait, if a type is composed entirely of Send or Sync types, then it |
| is Send or Sync. Almost all primitives are Send and Sync, and as a consequence |
| pretty much all types you'll ever interact with are Send and Sync. |
| |
| Major exceptions include: |
| |
| * raw pointers are neither Send nor Sync (because they have no safety guards). |
| * `UnsafeCell` isn't Sync (and therefore `Cell` and `RefCell` aren't). |
| * `Rc` isn't Send or Sync (because the refcount is shared and unsynchronized). |
| |
| `Rc` and `UnsafeCell` are very fundamentally not thread-safe: they enable |
| unsynchronized shared mutable state. However raw pointers are, strictly |
| speaking, marked as thread-unsafe as more of a *lint*. Doing anything useful |
| with a raw pointer requires dereferencing it, which is already unsafe. In that |
| sense, one could argue that it would be "fine" for them to be marked as thread |
| safe. |
| |
| However it's important that they aren't thread-safe to prevent types that |
| contain them from being automatically marked as thread-safe. These types have |
| non-trivial untracked ownership, and it's unlikely that their author was |
| necessarily thinking hard about thread safety. In the case of `Rc`, we have a nice |
| example of a type that contains a `*mut` that is definitely not thread-safe. |
| |
| Types that aren't automatically derived can simply implement them if desired: |
| |
| ```rust |
| struct MyBox(*mut u8); |
| |
| unsafe impl Send for MyBox {} |
| unsafe impl Sync for MyBox {} |
| ``` |
| |
| In the *incredibly rare* case that a type is inappropriately automatically |
| derived to be Send or Sync, then one can also unimplement Send and Sync: |
| |
| ```rust |
| #![feature(negative_impls)] |
| |
| // I have some magic semantics for some synchronization primitive! |
| struct SpecialThreadToken(u8); |
| |
| impl !Send for SpecialThreadToken {} |
| impl !Sync for SpecialThreadToken {} |
| ``` |
| |
| Note that *in and of itself* it is impossible to incorrectly derive Send and |
| Sync. Only types that are ascribed special meaning by other unsafe code can |
| possibly cause trouble by being incorrectly Send or Sync. |
| |
| Most uses of raw pointers should be encapsulated behind a sufficient abstraction |
| that Send and Sync can be derived. For instance all of Rust's standard |
| collections are Send and Sync (when they contain Send and Sync types) in spite |
| of their pervasive use of raw pointers to manage allocations and complex ownership. |
| Similarly, most iterators into these collections are Send and Sync because they |
| largely behave like an `&` or `&mut` into the collection. |
| |
| ## Example |
| |
| [`Box`][box-doc] is implemented as its own special intrinsic type by the |
| compiler for [various reasons][box-is-special], but we can implement something |
| with similar-ish behavior ourselves to see an example of when it is sound to |
| implement Send and Sync. Let's call it a `Carton`. |
| |
| We start by writing code to take a value allocated on the stack and transfer it |
| to the heap. |
| |
| ```rust |
| # pub mod libc { |
| # pub use ::std::os::raw::{c_int, c_void}; |
| # #[allow(non_camel_case_types)] |
| # pub type size_t = usize; |
| # extern "C" { pub fn posix_memalign(memptr: *mut *mut c_void, align: size_t, size: size_t) -> c_int; } |
| # } |
| use std::{ |
| mem::{align_of, size_of}, |
| ptr, |
| cmp::max, |
| }; |
| |
| struct Carton<T>(ptr::NonNull<T>); |
| |
| impl<T> Carton<T> { |
| pub fn new(value: T) -> Self { |
| // Allocate enough memory on the heap to store one T. |
| assert_ne!(size_of::<T>(), 0, "Zero-sized types are out of the scope of this example"); |
| let mut memptr: *mut T = ptr::null_mut(); |
| unsafe { |
| let ret = libc::posix_memalign( |
| (&mut memptr as *mut *mut T).cast(), |
| max(align_of::<T>(), size_of::<usize>()), |
| size_of::<T>() |
| ); |
| assert_eq!(ret, 0, "Failed to allocate or invalid alignment"); |
| }; |
| |
| // NonNull is just a wrapper that enforces that the pointer isn't null. |
| let ptr = { |
| // Safety: memptr is dereferenceable because we created it from a |
| // reference and have exclusive access. |
| ptr::NonNull::new(memptr) |
| .expect("Guaranteed non-null if posix_memalign returns 0") |
| }; |
| |
| // Move value from the stack to the location we allocated on the heap. |
| unsafe { |
| // Safety: If non-null, posix_memalign gives us a ptr that is valid |
| // for writes and properly aligned. |
| ptr.as_ptr().write(value); |
| } |
| |
| Self(ptr) |
| } |
| } |
| ``` |
| |
| This isn't very useful, because once our users give us a value they have no way |
| to access it. [`Box`][box-doc] implements [`Deref`][deref-doc] and |
| [`DerefMut`][deref-mut-doc] so that you can access the inner value. Let's do |
| that. |
| |
| ```rust |
| use std::ops::{Deref, DerefMut}; |
| |
| # struct Carton<T>(std::ptr::NonNull<T>); |
| # |
| impl<T> Deref for Carton<T> { |
| type Target = T; |
| |
| fn deref(&self) -> &Self::Target { |
| unsafe { |
| // Safety: The pointer is aligned, initialized, and dereferenceable |
| // by the logic in [`Self::new`]. We require readers to borrow the |
| // Carton, and the lifetime of the return value is elided to the |
| // lifetime of the input. This means the borrow checker will |
| // enforce that no one can mutate the contents of the Carton until |
| // the reference returned is dropped. |
| self.0.as_ref() |
| } |
| } |
| } |
| |
| impl<T> DerefMut for Carton<T> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| unsafe { |
| // Safety: The pointer is aligned, initialized, and dereferenceable |
| // by the logic in [`Self::new`]. We require writers to mutably |
| // borrow the Carton, and the lifetime of the return value is |
| // elided to the lifetime of the input. This means the borrow |
| // checker will enforce that no one else can access the contents |
| // of the Carton until the mutable reference returned is dropped. |
| self.0.as_mut() |
| } |
| } |
| } |
| ``` |
| |
| Finally, let's think about whether our `Carton` is Send and Sync. Something can |
| safely be Send unless it shares mutable state with something else without |
| enforcing exclusive access to it. Each `Carton` has a unique pointer, so |
| we're good. |
| |
| ```rust |
| # struct Carton<T>(std::ptr::NonNull<T>); |
| // Safety: No one besides us has the raw pointer, so we can safely transfer the |
| // Carton to another thread if T can be safely transferred. |
| unsafe impl<T> Send for Carton<T> where T: Send {} |
| ``` |
| |
| What about Sync? For `Carton` to be Sync we have to enforce that you can't |
| write to something stored in a `&Carton` while that same something could be read |
| or written to from another `&Carton`. Since you need an `&mut Carton` to |
| write to the pointer, and the borrow checker enforces that mutable |
| references must be exclusive, there are no soundness issues making `Carton` |
| sync either. |
| |
| ```rust |
| # struct Carton<T>(std::ptr::NonNull<T>); |
| // Safety: Since there exists a public way to go from a `&Carton<T>` to a `&T` |
| // in an unsynchronized fashion (such as `Deref`), then `Carton<T>` can't be |
| // `Sync` if `T` isn't. |
| // Conversely, `Carton` itself does not use any interior mutability whatsoever: |
| // all the mutations are performed through an exclusive reference (`&mut`). This |
| // means it suffices that `T` be `Sync` for `Carton<T>` to be `Sync`: |
| unsafe impl<T> Sync for Carton<T> where T: Sync {} |
| ``` |
| |
| When we assert our type is Send and Sync we usually need to enforce that every |
| contained type is Send and Sync. When writing custom types that behave like |
| standard library types we can assert that we have the same requirements. |
| For example, the following code asserts that a Carton is Send if the same |
| sort of Box would be Send, which in this case is the same as saying T is Send. |
| |
| ```rust |
| # struct Carton<T>(std::ptr::NonNull<T>); |
| unsafe impl<T> Send for Carton<T> where Box<T>: Send {} |
| ``` |
| |
| Right now `Carton<T>` has a memory leak, as it never frees the memory it allocates. |
| Once we fix that we have a new requirement we have to ensure we meet to be Send: |
| we need to know `free` can be called on a pointer that was yielded by an |
| allocation done on another thread. We can check this is true in the docs for |
| [`libc::free`][libc-free-docs]. |
| |
| ```rust |
| # struct Carton<T>(std::ptr::NonNull<T>); |
| # mod libc { |
| # pub use ::std::os::raw::c_void; |
| # extern "C" { pub fn free(p: *mut c_void); } |
| # } |
| impl<T> Drop for Carton<T> { |
| fn drop(&mut self) { |
| unsafe { |
| libc::free(self.0.as_ptr().cast()); |
| } |
| } |
| } |
| ``` |
| |
| A nice example where this does not happen is with a MutexGuard: notice how |
| [it is not Send][mutex-guard-not-send-docs-rs]. The implementation of MutexGuard |
| [uses libraries][mutex-guard-not-send-comment] that require you to ensure you |
| don't try to free a lock that you acquired in a different thread. If you were |
| able to Send a MutexGuard to another thread the destructor would run in the |
| thread you sent it to, violating the requirement. MutexGuard can still be Sync |
| because all you can send to another thread is an `&MutexGuard` and dropping a |
| reference does nothing. |
| |
| TODO: better explain what can or can't be Send or Sync. Sufficient to appeal |
| only to data races? |
| |
| [unsafe traits]: safe-unsafe-meaning.html |
| [box-doc]: https://doc.rust-lang.org/std/boxed/struct.Box.html |
| [box-is-special]: https://manishearth.github.io/blog/2017/01/10/rust-tidbits-box-is-special/ |
| [deref-doc]: https://doc.rust-lang.org/core/ops/trait.Deref.html |
| [deref-mut-doc]: https://doc.rust-lang.org/core/ops/trait.DerefMut.html |
| [mutex-guard-not-send-docs-rs]: https://doc.rust-lang.org/std/sync/struct.MutexGuard.html#impl-Send |
| [mutex-guard-not-send-comment]: https://github.com/rust-lang/rust/issues/23465#issuecomment-82730326 |
| [libc-free-docs]: https://linux.die.net/man/3/free |