Skip to main content

fragile/
semisticky.rs

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
14/// A [`SemiSticky<T>`] keeps a value T stored in a thread if it has a drop.
15///
16/// This is a combined version of [`Fragile`] and [`Sticky`].  If the type
17/// does not have a drop it will effectively be a [`Fragile`], otherwise it
18/// will be internally behave like a [`Sticky`].
19///
20/// This type requires `T: 'static` for the same reasons as [`Sticky`] and
21/// exposes the value only through scoped callbacks. Like [`Sticky`], it is
22/// [`Unpin`] only if `T` is `Unpin`.
23pub struct SemiSticky<T: 'static> {
24    inner: SemiStickyImpl<T>,
25}
26
27impl<T> SemiSticky<T> {
28    /// Creates a new [`SemiSticky`] wrapping a `value`.
29    ///
30    /// The value that is moved into the `SemiSticky` can be non `Send` and
31    /// will be anchored to the thread that created the object.  If the
32    /// sticky wrapper type ends up being send from thread to thread
33    /// only the original thread can interact with the value.  In case the
34    /// value does not have `Drop` it will be stored in the [`Fragile`]
35    /// instead.
36    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    /// Returns `true` if the access is valid.
47    ///
48    /// This will be `false` if the value was sent to another thread. For values
49    /// stored in [`Sticky`], it is also `false` once their registry starts being
50    /// destroyed. Values without drop glue do not depend on the registry.
51    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    /// Consumes the [`SemiSticky`], returning the wrapped value.
59    ///
60    /// # Panics
61    ///
62    /// Panics if called from a different thread than the one where the
63    /// original value was created.
64    #[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    /// Consumes the [`SemiSticky`], returning the wrapped value if successful.
73    ///
74    /// The wrapped value is returned if this is called from the same thread
75    /// as the one where the original value was created, otherwise the
76    /// [`SemiSticky`] is returned as `Err(self)`.
77    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    /// Invokes a callback with a shared reference to the wrapped value.
89    ///
90    /// The callback may return owned data, but it cannot return a reference
91    /// derived from the wrapped value.
92    ///
93    /// ```
94    /// use fragile::SemiSticky;
95    ///
96    /// let value = SemiSticky::new(String::from("hello"));
97    /// let length = value.with(String::len);
98    /// assert_eq!(length, 5);
99    /// ```
100    ///
101    /// # Panics
102    ///
103    /// Panics if the calling thread is not the one that wrapped the value, or
104    /// if its thread-local registry has been destroyed.
105    /// For a non-panicking variant, use [`try_with`](Self::try_with).
106    #[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    /// Invokes a callback with an exclusive reference to the wrapped value.
118    ///
119    /// The callback may return owned data, but it cannot return a reference
120    /// derived from the wrapped value.
121    ///
122    /// # Panics
123    ///
124    /// Panics if the calling thread is not the one that wrapped the value, or
125    /// if its thread-local registry has been destroyed.
126    /// For a non-panicking variant, use [`try_with_mut`](Self::try_with_mut).
127    #[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    /// Invokes a callback with a shared reference to the wrapped value.
139    ///
140    /// As with [`with`](Self::with), the callback cannot return a reference
141    /// derived from the wrapped value.
142    ///
143    /// Returns [`InvalidThreadAccess`] without invoking the callback if
144    /// [`is_valid`](Self::is_valid) is `false`.
145    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    /// Invokes a callback with an exclusive reference to the wrapped value.
156    ///
157    /// As with [`with_mut`](Self::with_mut), the callback cannot return a
158    /// reference derived from the wrapped value.
159    ///
160    /// Returns [`InvalidThreadAccess`] without invoking the callback if
161    /// [`is_valid`](Self::is_valid) is `false`.
162    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                // moves it here but do not deallocate
354                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}