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}