1use std::cmp;
2use std::fmt;
3use std::mem;
4
5use crate::errors::InvalidThreadAccess;
6use crate::fragile::Fragile;
7use crate::sticky::Sticky;
8
9enum SemiStickyImpl<T: 'static> {
10 Fragile(Box<Fragile<T>>),
11 Sticky(Sticky<T>),
12}
13
14pub struct SemiSticky<T: 'static> {
24 inner: SemiStickyImpl<T>,
25}
26
27impl<T> SemiSticky<T> {
28 pub fn new(value: T) -> Self {
37 SemiSticky {
38 inner: if mem::needs_drop::<T>() {
39 SemiStickyImpl::Sticky(Sticky::new(value))
40 } else {
41 SemiStickyImpl::Fragile(Box::new(Fragile::new(value)))
42 },
43 }
44 }
45
46 pub fn is_valid(&self) -> bool {
52 match self.inner {
53 SemiStickyImpl::Fragile(ref inner) => inner.is_valid(),
54 SemiStickyImpl::Sticky(ref inner) => inner.is_valid(),
55 }
56 }
57
58 #[track_caller]
65 pub fn into_inner(self) -> T {
66 match self.inner {
67 SemiStickyImpl::Fragile(inner) => inner.into_inner(),
68 SemiStickyImpl::Sticky(inner) => inner.into_inner(),
69 }
70 }
71
72 pub fn try_into_inner(self) -> Result<T, Self> {
78 match self.inner {
79 SemiStickyImpl::Fragile(inner) => inner.try_into_inner().map_err(|inner| SemiSticky {
80 inner: SemiStickyImpl::Fragile(Box::new(inner)),
81 }),
82 SemiStickyImpl::Sticky(inner) => inner.try_into_inner().map_err(|inner| SemiSticky {
83 inner: SemiStickyImpl::Sticky(inner),
84 }),
85 }
86 }
87
88 #[track_caller]
107 pub fn with<R, F>(&self, f: F) -> R
108 where
109 F: for<'a> FnOnce(&'a T) -> R,
110 {
111 match self.inner {
112 SemiStickyImpl::Fragile(ref inner) => f(inner.get()),
113 SemiStickyImpl::Sticky(ref inner) => inner.with(f),
114 }
115 }
116
117 #[track_caller]
128 pub fn with_mut<R, F>(&mut self, f: F) -> R
129 where
130 F: for<'a> FnOnce(&'a mut T) -> R,
131 {
132 match self.inner {
133 SemiStickyImpl::Fragile(ref mut inner) => f(inner.get_mut()),
134 SemiStickyImpl::Sticky(ref mut inner) => inner.with_mut(f),
135 }
136 }
137
138 pub fn try_with<R, F>(&self, f: F) -> Result<R, InvalidThreadAccess>
146 where
147 F: for<'a> FnOnce(&'a T) -> R,
148 {
149 match self.inner {
150 SemiStickyImpl::Fragile(ref inner) => inner.try_get().map(f),
151 SemiStickyImpl::Sticky(ref inner) => inner.try_with(f),
152 }
153 }
154
155 pub fn try_with_mut<R, F>(&mut self, f: F) -> Result<R, InvalidThreadAccess>
163 where
164 F: for<'a> FnOnce(&'a mut T) -> R,
165 {
166 match self.inner {
167 SemiStickyImpl::Fragile(ref mut inner) => inner.try_get_mut().map(f),
168 SemiStickyImpl::Sticky(ref mut inner) => inner.try_with_mut(f),
169 }
170 }
171}
172
173impl<T> From<T> for SemiSticky<T> {
174 #[inline]
175 fn from(t: T) -> SemiSticky<T> {
176 SemiSticky::new(t)
177 }
178}
179
180impl<T: Clone> Clone for SemiSticky<T> {
181 #[inline]
182 #[track_caller]
183 fn clone(&self) -> SemiSticky<T> {
184 SemiSticky::new(self.with(Clone::clone))
185 }
186}
187
188impl<T: Default> Default for SemiSticky<T> {
189 #[inline]
190 fn default() -> SemiSticky<T> {
191 SemiSticky::new(T::default())
192 }
193}
194
195impl<T: PartialEq> PartialEq for SemiSticky<T> {
196 #[inline]
197 #[track_caller]
198 fn eq(&self, other: &SemiSticky<T>) -> bool {
199 self.with(|this| other.with(|other| this == other))
200 }
201}
202
203impl<T: Eq> Eq for SemiSticky<T> {}
204
205impl<T: PartialOrd> PartialOrd for SemiSticky<T> {
206 #[inline]
207 #[track_caller]
208 fn partial_cmp(&self, other: &SemiSticky<T>) -> Option<cmp::Ordering> {
209 self.with(|this| other.with(|other| this.partial_cmp(other)))
210 }
211
212 #[inline]
213 #[track_caller]
214 fn lt(&self, other: &SemiSticky<T>) -> bool {
215 self.with(|this| other.with(|other| this < other))
216 }
217
218 #[inline]
219 #[track_caller]
220 fn le(&self, other: &SemiSticky<T>) -> bool {
221 self.with(|this| other.with(|other| this <= other))
222 }
223
224 #[inline]
225 #[track_caller]
226 fn gt(&self, other: &SemiSticky<T>) -> bool {
227 self.with(|this| other.with(|other| this > other))
228 }
229
230 #[inline]
231 #[track_caller]
232 fn ge(&self, other: &SemiSticky<T>) -> bool {
233 self.with(|this| other.with(|other| this >= other))
234 }
235}
236
237impl<T: Ord> Ord for SemiSticky<T> {
238 #[inline]
239 #[track_caller]
240 fn cmp(&self, other: &SemiSticky<T>) -> cmp::Ordering {
241 self.with(|this| other.with(|other| this.cmp(other)))
242 }
243}
244
245impl<T: fmt::Display> fmt::Display for SemiSticky<T> {
246 #[track_caller]
247 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
248 self.with(|value| fmt::Display::fmt(value, f))
249 }
250}
251
252impl<T: fmt::Debug> fmt::Debug for SemiSticky<T> {
253 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
254 if self.is_valid() {
255 self.with(|value| f.debug_struct("SemiSticky").field("value", value).finish())
256 } else {
257 struct InvalidPlaceholder;
258 impl fmt::Debug for InvalidPlaceholder {
259 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260 f.write_str("<invalid thread>")
261 }
262 }
263
264 f.debug_struct("SemiSticky")
265 .field("value", &InvalidPlaceholder)
266 .finish()
267 }
268 }
269}
270
271#[test]
272fn test_basic() {
273 use std::thread;
274 let val = SemiSticky::new(true);
275 assert_eq!(val.to_string(), "true");
276 assert!(val.with(|value| *value));
277 assert!(val.try_with(|value| *value).unwrap());
278 thread::spawn(move || {
279 assert!(val
280 .try_with(|_| panic!("callback ran on the wrong thread"))
281 .is_err());
282 })
283 .join()
284 .unwrap();
285}
286
287#[test]
288fn test_mut() {
289 let mut val = SemiSticky::new(true);
290 val.with_mut(|value| *value = false);
291 assert_eq!(val.to_string(), "false");
292 assert!(!val.with(|value| *value));
293 assert!(val
294 .try_with_mut(|value| {
295 *value = true;
296 *value
297 })
298 .unwrap());
299}
300
301#[test]
302#[should_panic]
303fn test_access_other_thread() {
304 use std::thread;
305 let mut val = SemiSticky::new(true);
306 thread::spawn(move || {
307 assert!(val
308 .try_with_mut(|_| panic!("mutable callback ran on the wrong thread"))
309 .is_err());
310 val.with(|_| ());
311 })
312 .join()
313 .unwrap();
314}
315
316#[test]
317fn test_drop_same_thread() {
318 use std::sync::atomic::{AtomicBool, Ordering};
319 use std::sync::Arc;
320 let was_called = Arc::new(AtomicBool::new(false));
321 struct X(Arc<AtomicBool>);
322 impl Drop for X {
323 fn drop(&mut self) {
324 self.0.store(true, Ordering::SeqCst);
325 }
326 }
327 let val = SemiSticky::new(X(was_called.clone()));
328 mem::drop(val);
329 assert!(was_called.load(Ordering::SeqCst));
330}
331
332#[test]
333fn test_noop_drop_elsewhere() {
334 use std::sync::atomic::{AtomicBool, Ordering};
335 use std::sync::Arc;
336 use std::thread;
337
338 let was_called = Arc::new(AtomicBool::new(false));
339
340 {
341 let was_called = was_called.clone();
342 thread::spawn(move || {
343 struct X(Arc<AtomicBool>);
344 impl Drop for X {
345 fn drop(&mut self) {
346 self.0.store(true, Ordering::SeqCst);
347 }
348 }
349
350 let val = SemiSticky::new(X(was_called.clone()));
351 val.with(|_| ());
352 assert!(thread::spawn(move || {
353 val.try_with(|_| ()).ok();
355 })
356 .join()
357 .is_ok());
358
359 assert!(!was_called.load(Ordering::SeqCst));
360 })
361 .join()
362 .unwrap();
363 }
364
365 assert!(was_called.load(Ordering::SeqCst));
366}
367
368#[test]
369fn test_rc_sending() {
370 use std::rc::Rc;
371 use std::thread;
372 let val = SemiSticky::new(Rc::new(true));
373 thread::spawn(move || {
374 assert!(val
375 .try_with(|_| panic!("callback ran on the wrong thread"))
376 .is_err());
377 })
378 .join()
379 .unwrap();
380}