reactive_graph/
send_wrapper_ext.rs

1//! Additional wrapper utilities for [`send_wrapper::SendWrapper`].
2
3use send_wrapper::SendWrapper;
4use std::{
5    fmt::{Debug, Formatter},
6    hash,
7    ops::{Deref, DerefMut},
8};
9/// An optional value that can always be sent between threads, even if its inner value
10/// in the `Some(_)` case would not be threadsafe.
11///
12/// This struct can be derefenced to `Option<T>`.
13///
14/// If it has been given a local (`!Send`) value, that value is wrapped in a [`SendWrapper`], which
15/// allows sending it between threads but will panic if it is accessed or updated from a  
16/// thread other than the one on which it was created.
17///
18/// If it is created with `None` for a local (`!Send`) type, no `SendWrapper` is created until a
19/// value is provided via [`DerefMut`] or [`update`](SendOption::update).
20///
21/// ### Use Case
22/// This is useful for cases like browser-only types, which are `!Send` but cannot be constructed
23/// on the server anyway, and are only created in a single-threaded browser environment. The local
24/// `SendOption` can be created with its `None` variant and sent between threads without causing issues
25/// when it is dropped.
26///
27/// ### Panics
28/// Dereferencing or dropping `SendOption` panics under the following conditions:
29/// 1) It is created via [`new_local`](SendOption::new_local) (signifying a `!Send` inner type),
30/// 2) It has `Some(_)` value, and
31/// 3) It has been sent to a thread other than the one on which it was created.
32pub struct SendOption<T> {
33    inner: Inner<T>,
34}
35
36// SAFETY: `SendOption` can *only* be given a T in four ways
37// 1) via new(), which requires T: Send + Sync
38// 2) via new_local(), which wraps T in a SendWrapper if given Some(T)
39// 3) via deref_mut(), which creates a SendWrapper<Option<T>> as needed
40// 4) via update(), which either dereferences an existing SendWrapper
41//    or creates a new SendWrapper as needed
42unsafe impl<T> Send for SendOption<T> {}
43unsafe impl<T> Sync for SendOption<T> {}
44
45impl<T> PartialEq for SendOption<T>
46where
47    T: PartialEq,
48{
49    fn eq(&self, other: &Self) -> bool {
50        self.deref() == other.deref()
51    }
52}
53
54impl<T> Eq for SendOption<T> where T: Eq {}
55
56impl<T> PartialOrd for SendOption<T>
57where
58    T: PartialOrd,
59{
60    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
61        self.deref().partial_cmp(other.deref())
62    }
63}
64
65impl<T> hash::Hash for SendOption<T>
66where
67    T: hash::Hash,
68{
69    fn hash<H: hash::Hasher>(&self, state: &mut H) {
70        self.deref().hash(state);
71    }
72}
73
74enum Inner<T> {
75    /// A threadsafe value.
76    Threadsafe(Option<T>),
77    /// A non-threadsafe value. If accessed/dropped from a different thread in the Some() variant, it will panic.
78    Local(Option<SendWrapper<Option<T>>>),
79}
80
81impl<T> SendOption<T>
82where
83    T: Send + Sync,
84{
85    /// Create a new threadsafe value.
86    pub fn new(value: Option<T>) -> Self {
87        Self {
88            inner: Inner::Threadsafe(value),
89        }
90    }
91}
92
93impl<T> From<Option<T>> for SendOption<T>
94where
95    T: Send + Sync,
96{
97    fn from(value: Option<T>) -> Self {
98        Self::new(value)
99    }
100}
101
102impl<T> SendOption<T> {
103    /// Create a new non-threadsafe value.
104    pub fn new_local(value: Option<T>) -> Self {
105        Self {
106            inner: if let Some(value) = value {
107                Inner::Local(Some(SendWrapper::new(Some(value))))
108            } else {
109                Inner::Local(None)
110            },
111        }
112    }
113
114    /// Update a value in place with a callback.
115    ///
116    /// # Panics
117    /// If the value is [`Inner::Local`] and it is called from a different thread than the one the instance has been created with, it will panic.
118    pub fn update(&mut self, cb: impl FnOnce(&mut Option<T>)) {
119        match &mut self.inner {
120            Inner::Threadsafe(value) => cb(value),
121            Inner::Local(value) => match value {
122                Some(sw) => {
123                    cb(sw.deref_mut());
124                    if sw.is_none() {
125                        *value = None;
126                    }
127                }
128                None => {
129                    let mut inner = None;
130                    cb(&mut inner);
131                    if let Some(inner) = inner {
132                        *value = Some(SendWrapper::new(Some(inner)));
133                    }
134                }
135            },
136        }
137    }
138
139    /// Consume the value.
140    ///
141    /// # Panics
142    /// Panics if the [`Inner::Local`] variant and it is called from a different thread than the one the instance has been created with.
143    pub fn take(self) -> Option<T> {
144        match self.inner {
145            Inner::Threadsafe(value) => value,
146            Inner::Local(value) => value.and_then(|value| value.take()),
147        }
148    }
149}
150
151impl<T> Deref for SendOption<T> {
152    type Target = Option<T>;
153
154    fn deref(&self) -> &Self::Target {
155        match &self.inner {
156            Inner::Threadsafe(value) => value,
157            Inner::Local(value) => match value {
158                Some(value) => value.deref(),
159                None => &None,
160            },
161        }
162    }
163}
164
165impl<T> DerefMut for SendOption<T> {
166    fn deref_mut(&mut self) -> &mut Self::Target {
167        match &mut self.inner {
168            Inner::Threadsafe(value) => value,
169            Inner::Local(value) => match value {
170                Some(value) => value.deref_mut(),
171                None => {
172                    *value = Some(SendWrapper::new(None));
173                    value.as_mut().unwrap().deref_mut()
174                }
175            },
176        }
177    }
178}
179
180impl<T: Debug> Debug for SendOption<T> {
181    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
182        match &self.inner {
183            Inner::Threadsafe(value) => {
184                write!(f, "SendOption::Threadsafe({value:?})")
185            }
186            Inner::Local(value) => {
187                write!(f, "SendOption::Local({value:?})")
188            }
189        }
190    }
191}
192
193impl<T: Clone> Clone for SendOption<T> {
194    fn clone(&self) -> Self {
195        Self {
196            inner: match &self.inner {
197                Inner::Threadsafe(value) => Inner::Threadsafe(value.clone()),
198                Inner::Local(value) => Inner::Local(value.clone()),
199            },
200        }
201    }
202}