]>
Commit | Line | Data |
---|---|---|
8bb4bdeb | 1 | # Send and Sync |
c1a9b12d SL |
2 | |
3 | Not everything obeys inherited mutability, though. Some types allow you to | |
7cac9316 | 4 | have multiple aliases of a location in memory while mutating it. Unless these types use |
2c00a5a8 | 5 | synchronization to manage this access, they are absolutely not thread-safe. Rust |
e9174d1e | 6 | captures this through the `Send` and `Sync` traits. |
c1a9b12d SL |
7 | |
8 | * A type is Send if it is safe to send it to another thread. | |
29967ef6 | 9 | * A type is Sync if it is safe to share between threads (T is Sync if and only if `&T` is Send). |
c1a9b12d SL |
10 | |
11 | Send and Sync are fundamental to Rust's concurrency story. As such, a | |
12 | substantial amount of special tooling exists to make them work right. First and | |
e9174d1e SL |
13 | foremost, they're [unsafe traits]. This means that they are unsafe to |
14 | implement, and other unsafe code can assume that they are correctly | |
c1a9b12d SL |
15 | implemented. Since they're *marker traits* (they have no associated items like |
16 | methods), correctly implemented simply means that they have the intrinsic | |
17 | properties an implementor should have. Incorrectly implementing Send or Sync can | |
b039eaaf | 18 | cause Undefined Behavior. |
c1a9b12d SL |
19 | |
20 | Send and Sync are also automatically derived traits. This means that, unlike | |
21 | every other trait, if a type is composed entirely of Send or Sync types, then it | |
22 | is Send or Sync. Almost all primitives are Send and Sync, and as a consequence | |
23 | pretty much all types you'll ever interact with are Send and Sync. | |
24 | ||
25 | Major exceptions include: | |
26 | ||
27 | * raw pointers are neither Send nor Sync (because they have no safety guards). | |
28 | * `UnsafeCell` isn't Sync (and therefore `Cell` and `RefCell` aren't). | |
29 | * `Rc` isn't Send or Sync (because the refcount is shared and unsynchronized). | |
30 | ||
31 | `Rc` and `UnsafeCell` are very fundamentally not thread-safe: they enable | |
32 | unsynchronized shared mutable state. However raw pointers are, strictly | |
33 | speaking, marked as thread-unsafe as more of a *lint*. Doing anything useful | |
34 | with a raw pointer requires dereferencing it, which is already unsafe. In that | |
35 | sense, one could argue that it would be "fine" for them to be marked as thread | |
36 | safe. | |
37 | ||
2c00a5a8 XL |
38 | However it's important that they aren't thread-safe to prevent types that |
39 | contain them from being automatically marked as thread-safe. These types have | |
c1a9b12d | 40 | non-trivial untracked ownership, and it's unlikely that their author was |
2c00a5a8 XL |
41 | necessarily thinking hard about thread safety. In the case of `Rc`, we have a nice |
42 | example of a type that contains a `*mut` that is definitely not thread-safe. | |
c1a9b12d SL |
43 | |
44 | Types that aren't automatically derived can simply implement them if desired: | |
45 | ||
46 | ```rust | |
47 | struct MyBox(*mut u8); | |
48 | ||
49 | unsafe impl Send for MyBox {} | |
50 | unsafe impl Sync for MyBox {} | |
51 | ``` | |
52 | ||
53 | In the *incredibly rare* case that a type is inappropriately automatically | |
54 | derived to be Send or Sync, then one can also unimplement Send and Sync: | |
55 | ||
56 | ```rust | |
ba9703b0 | 57 | #![feature(negative_impls)] |
c1a9b12d SL |
58 | |
59 | // I have some magic semantics for some synchronization primitive! | |
60 | struct SpecialThreadToken(u8); | |
61 | ||
62 | impl !Send for SpecialThreadToken {} | |
63 | impl !Sync for SpecialThreadToken {} | |
64 | ``` | |
65 | ||
66 | Note that *in and of itself* it is impossible to incorrectly derive Send and | |
67 | Sync. Only types that are ascribed special meaning by other unsafe code can | |
29967ef6 | 68 | possibly cause trouble by being incorrectly Send or Sync. |
c1a9b12d SL |
69 | |
70 | Most uses of raw pointers should be encapsulated behind a sufficient abstraction | |
71 | that Send and Sync can be derived. For instance all of Rust's standard | |
72 | collections are Send and Sync (when they contain Send and Sync types) in spite | |
73 | of their pervasive use of raw pointers to manage allocations and complex ownership. | |
74 | Similarly, most iterators into these collections are Send and Sync because they | |
75 | largely behave like an `&` or `&mut` into the collection. | |
76 | ||
cdc7bbd5 XL |
77 | ## Example |
78 | ||
79 | [`Box`][box-doc] is implemented as it's own special intrinsic type by the | |
80 | compiler for [various reasons][box-is-special], but we can implement something | |
81 | with similar-ish behavior ourselves to see an example of when it is sound to | |
82 | implement Send and Sync. Let's call it a `Carton`. | |
83 | ||
84 | We start by writing code to take a value allocated on the stack and transfer it | |
85 | to the heap. | |
86 | ||
87 | ```rust | |
88 | # pub mod libc { | |
89 | # pub use ::std::os::raw::{c_int, c_void}; | |
90 | # #[allow(non_camel_case_types)] | |
91 | # pub type size_t = usize; | |
92 | # extern "C" { pub fn posix_memalign(memptr: *mut *mut c_void, align: size_t, size: size_t) -> c_int; } | |
93 | # } | |
94 | use std::{ | |
95 | mem::{align_of, size_of}, | |
96 | ptr, | |
97 | }; | |
98 | ||
99 | struct Carton<T>(ptr::NonNull<T>); | |
100 | ||
101 | impl<T> Carton<T> { | |
102 | pub fn new(value: T) -> Self { | |
103 | // Allocate enough memory on the heap to store one T. | |
104 | assert_ne!(size_of::<T>(), 0, "Zero-sized types are out of the scope of this example"); | |
c295e0f8 | 105 | let mut memptr: *mut T = ptr::null_mut(); |
cdc7bbd5 XL |
106 | unsafe { |
107 | let ret = libc::posix_memalign( | |
108 | (&mut memptr).cast(), | |
109 | align_of::<T>(), | |
110 | size_of::<T>() | |
111 | ); | |
112 | assert_eq!(ret, 0, "Failed to allocate or invalid alignment"); | |
113 | }; | |
114 | ||
115 | // NonNull is just a wrapper that enforces that the pointer isn't null. | |
c295e0f8 | 116 | let ptr = { |
cdc7bbd5 XL |
117 | // Safety: memptr is dereferenceable because we created it from a |
118 | // reference and have exclusive access. | |
c295e0f8 | 119 | ptr::NonNull::new(memptr) |
cdc7bbd5 XL |
120 | .expect("Guaranteed non-null if posix_memalign returns 0") |
121 | }; | |
122 | ||
123 | // Move value from the stack to the location we allocated on the heap. | |
124 | unsafe { | |
125 | // Safety: If non-null, posix_memalign gives us a ptr that is valid | |
126 | // for writes and properly aligned. | |
127 | ptr.as_ptr().write(value); | |
128 | } | |
129 | ||
130 | Self(ptr) | |
131 | } | |
132 | } | |
133 | ``` | |
134 | ||
135 | This isn't very useful, because once our users give us a value they have no way | |
136 | to access it. [`Box`][box-doc] implements [`Deref`][deref-doc] and | |
137 | [`DerefMut`][deref-mut-doc] so that you can access the inner value. Let's do | |
138 | that. | |
139 | ||
140 | ```rust | |
141 | use std::ops::{Deref, DerefMut}; | |
142 | ||
143 | # struct Carton<T>(std::ptr::NonNull<T>); | |
144 | # | |
145 | impl<T> Deref for Carton<T> { | |
146 | type Target = T; | |
147 | ||
148 | fn deref(&self) -> &Self::Target { | |
149 | unsafe { | |
150 | // Safety: The pointer is aligned, initialized, and dereferenceable | |
151 | // by the logic in [`Self::new`]. We require writers to borrow the | |
152 | // Carton, and the lifetime of the return value is elided to the | |
153 | // lifetime of the input. This means the borrow checker will | |
154 | // enforce that no one can mutate the contents of the Carton until | |
155 | // the reference returned is dropped. | |
156 | self.0.as_ref() | |
157 | } | |
158 | } | |
159 | } | |
160 | ||
161 | impl<T> DerefMut for Carton<T> { | |
162 | fn deref_mut(&mut self) -> &mut Self::Target { | |
163 | unsafe { | |
164 | // Safety: The pointer is aligned, initialized, and dereferenceable | |
165 | // by the logic in [`Self::new`]. We require writers to mutably | |
166 | // borrow the Carton, and the lifetime of the return value is | |
167 | // elided to the lifetime of the input. This means the borrow | |
168 | // checker will enforce that no one else can access the contents | |
169 | // of the Carton until the mutable reference returned is dropped. | |
170 | self.0.as_mut() | |
171 | } | |
172 | } | |
173 | } | |
174 | ``` | |
175 | ||
176 | Finally, let's think about whether our `Carton` is Send and Sync. Something can | |
177 | safely be Send unless it shares mutable state with something else without | |
178 | enforcing exclusive access to it. Each `Carton` has a unique pointer, so | |
179 | we're good. | |
180 | ||
181 | ```rust | |
182 | # struct Carton<T>(std::ptr::NonNull<T>); | |
183 | // Safety: No one besides us has the raw pointer, so we can safely transfer the | |
184 | // Carton to another thread if T can be safely transferred. | |
185 | unsafe impl<T> Send for Carton<T> where T: Send {} | |
186 | ``` | |
187 | ||
188 | What about Sync? For `Carton` to be Sync we have to enforce that you can't | |
189 | write to something stored in a `&Carton` while that same something could be read | |
190 | or written to from another `&Carton`. Since you need an `&mut Carton` to | |
191 | write to the pointer, and the borrow checker enforces that mutable | |
192 | references must be exclusive, there are no soundness issues making `Carton` | |
193 | sync either. | |
194 | ||
195 | ```rust | |
196 | # struct Carton<T>(std::ptr::NonNull<T>); | |
197 | // Safety: Since there exists a public way to go from a `&Carton<T>` to a `&T` | |
198 | // in an unsynchronized fashion (such as `Deref`), then `Carton<T>` can't be | |
199 | // `Sync` if `T` isn't. | |
200 | // Conversely, `Carton` itself does not use any interior mutability whatsoever: | |
201 | // all the mutations are performed through an exclusive reference (`&mut`). This | |
202 | // means it suffices that `T` be `Sync` for `Carton<T>` to be `Sync`: | |
203 | unsafe impl<T> Sync for Carton<T> where T: Sync {} | |
204 | ``` | |
205 | ||
206 | When we assert our type is Send and Sync we usually need to enforce that every | |
207 | contained type is Send and Sync. When writing custom types that behave like | |
208 | standard library types we can assert that we have the same requirements. | |
209 | For example, the following code asserts that a Carton is Send if the same | |
210 | sort of Box would be Send, which in this case is the same as saying T is Send. | |
211 | ||
212 | ```rust | |
213 | # struct Carton<T>(std::ptr::NonNull<T>); | |
214 | unsafe impl<T> Send for Carton<T> where Box<T>: Send {} | |
215 | ``` | |
216 | ||
217 | Right now `Carton<T>` has a memory leak, as it never frees the memory it allocates. | |
218 | Once we fix that we have a new requirement we have to ensure we meet to be Send: | |
219 | we need to know `free` can be called on a pointer that was yielded by an | |
220 | allocation done on another thread. We can check this is true in the docs for | |
221 | [`libc::free`][libc-free-docs]. | |
222 | ||
223 | ```rust | |
224 | # struct Carton<T>(std::ptr::NonNull<T>); | |
225 | # mod libc { | |
226 | # pub use ::std::os::raw::c_void; | |
227 | # extern "C" { pub fn free(p: *mut c_void); } | |
228 | # } | |
229 | impl<T> Drop for Carton<T> { | |
230 | fn drop(&mut self) { | |
231 | unsafe { | |
232 | libc::free(self.0.as_ptr().cast()); | |
233 | } | |
234 | } | |
235 | } | |
236 | ``` | |
237 | ||
238 | A nice example where this does not happen is with a MutexGuard: notice how | |
239 | [it is not Send][mutex-guard-not-send-docs-rs]. The implementation of MutexGuard | |
240 | [uses libraries][mutex-guard-not-send-comment] that require you to ensure you | |
241 | don't try to free a lock that you acquired in a different thread. If you were | |
242 | able to Send a MutexGuard to another thread the destructor would run in the | |
243 | thread you sent it to, violating the requirement. MutexGuard can still be Sync | |
244 | because all you can send to another thread is an `&MutexGuard` and dropping a | |
245 | reference does nothing. | |
246 | ||
c1a9b12d SL |
247 | TODO: better explain what can or can't be Send or Sync. Sufficient to appeal |
248 | only to data races? | |
249 | ||
250 | [unsafe traits]: safe-unsafe-meaning.html | |
cdc7bbd5 XL |
251 | [box-doc]: https://doc.rust-lang.org/std/boxed/struct.Box.html |
252 | [box-is-special]: https://manishearth.github.io/blog/2017/01/10/rust-tidbits-box-is-special/ | |
253 | [deref-doc]: https://doc.rust-lang.org/core/ops/trait.Deref.html | |
254 | [deref-mut-doc]: https://doc.rust-lang.org/core/ops/trait.DerefMut.html | |
255 | [mutex-guard-not-send-docs-rs]: https://doc.rust-lang.org/std/sync/struct.MutexGuard.html#impl-Send | |
256 | [mutex-guard-not-send-comment]: https://github.com/rust-lang/rust/issues/23465#issuecomment-82730326 | |
257 | [libc-free-docs]: https://linux.die.net/man/3/free |