@@ -74,7 +74,184 @@ of their pervasive use of raw pointers to manage allocations and complex ownersh
74
74
Similarly, most iterators into these collections are Send and Sync because they
75
75
largely behave like an ` & ` or ` &mut ` into the collection.
76
76
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" );
105
+ let mut memptr = ptr :: null_mut () as * mut T ;
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.
116
+ let mut ptr = unsafe {
117
+ // Safety: memptr is dereferenceable because we created it from a
118
+ // reference and have exclusive access.
119
+ ptr :: NonNull :: new (memptr . cast :: <T >())
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
+
77
247
TODO: better explain what can or can't be Send or Sync. Sufficient to appeal
78
248
only to data races?
79
249
80
250
[ unsafe traits ] : safe-unsafe-meaning.html
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
0 commit comments