thread_lock/lib.rs
1#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
2#![warn(missing_docs)]
3
4//! This crate introduces the niche [`ThreadLock`] struct.
5//! This struct stores arbitrary data, but only allows access to it from a specific thread at runtime; in exchange, the [`ThreadLock`] itself is [`Send`] and [`Sync`].
6//!
7//! This has very limited usage, but occasionally it may be useful for cases where some parts of a struct must be multithreaded, while other parts cannot be.
8//! Often, these should be split into distinct structs (one of which is [`Sync`] while the other is not), but this may occasionally be a simpler option.
9//!
10//! A (contrived) example similar to an actual usage I had:
11//!
12//! ```rust
13//! # use std::rc::Rc;
14//! # use thread_lock::ThreadLock;
15//!
16//! struct A; // A: Sync
17//!
18//! struct B;
19//!
20//! # fn construct_ab() -> (A, Rc<B>) {
21//! # (A, Rc::new(B))
22//! # }
23//! #
24//! # fn do_something_with_a(a: &A) {}
25//! #
26//! # fn do_something_with_b(b: &B) {}
27//! #
28//! pub struct AB {
29//!
30//! a: A,
31//! b: ThreadLock<Rc<B>>
32//!
33//! }
34//!
35//! impl AB {
36//!
37//! pub fn new() -> Self {
38//! // note that Rc is neither Send nor Sync
39//! let (a, b): (A, Rc<B>) = construct_ab();
40//! Self { a, b: ThreadLock::new(b) }
41//! }
42//!
43//! pub fn foo(&self) { // any thread is allowed to call AB::foo
44//! do_something_with_a(&self.a);
45//! }
46//!
47//! pub fn foo_and_bar(&self) {
48//! let b = self.b.try_get().expect("foo_and_bar is only allowed on the same thread that AB was constructed");
49//! do_something_with_a(&self.a);
50//! do_something_with_b(b);
51//! }
52//!
53//! }
54//! ```
55//!
56//! The notable features of this example:
57//! 1. I want to be able to do some of the things `AB` can do on all threads, so I want `AB` to be [`Sync`].
58//! 2. Some of the things AB can do (namely, `foo_and_bar`) require `AB` to have resources (namely, `B`) that cannot be shared among threads, as well as the multi-threaded resources.
59//! 3. `A` and `B` can only be constructed together; this is less important, but it can make it harder or less ergonomic to split `AB` into distinct structs.
60
61use std::{error::Error, fmt::{self, Debug, Display, Formatter}, thread::{current, ThreadId}};
62
63/// A `ThreadLock<T>` contains a value of type `T`, but only allows access to it from one thread, checked at runtime; in exchange, every `ThreadLock<T>` is both [`Send`] and [`Sync`]
64///
65/// Logically, this can be thought of as similar to a [`RefCell`](std::cell::RefCell):
66/// a [`RefCell`](std::cell::RefCell) waives (some of) the restrictions of borrow checker at compile time, but enforces them at runtime;
67/// likewise, a `ThreadLock` waives (some of) the restrictions of [`Send`] and [`Sync`] as compile time, but enforces them at runtime.
68///
69/// The methods [`ThreadLock::into_inner_unlocked`], [`ThreadLock::get_unlocked`], and [`ThreadLock::get_unlocked_mut`], henceforth reffered to collectively as the `*_unlocked` methods,
70/// allow users to fall back to compile-time checking of [`Send`] and [`Sync`].
71/// Because of these methods, users cannot assume that other threads have not observed the value in a `ThreadLock`, unless they are certain that that value is not [`Send`]
72/// (all of the `*_unlocked` methods require that the value is [`Send`]; [`ThreadLock::get_unlocked`] addionally requires that the value is [`Sync`]).
73/// Users can, however, assume that the value has only been observed in ways that fulfil the contract given by the presence or absence of [`Send`] and [`Sync`] for that type.
74///
75/// # Examples
76///
77/// ```rust
78/// # use std::thread;
79/// # use std::sync::Arc;
80/// # use thread_lock::{ThreadLock, WrongThreadError};
81/// let message = 42i32; // i32 is Send and Sync, so all the `*_unlocked` methods are available
82/// let locked_message = Arc::new(ThreadLock::new(message));
83/// let locked_message2 = Arc::clone(&locked_message);
84/// thread::spawn(move || {
85/// assert_eq!(locked_message2.try_get(), Err(WrongThreadError)); // non-`*_unlocked` methods perform runtime checks, even for Send and Sync types
86/// assert_eq!(locked_message2.get_unlocked(), &42);
87/// });
88/// assert_eq!(locked_message.try_get(), Ok(&42));
89/// ```
90pub struct ThreadLock<T: ?Sized> {
91
92 thread: ThreadId,
93 value: T
94
95}
96
97impl<T> ThreadLock<T> {
98
99 /// Constructs a new `ThreadLock`, locked to the current thread; that is, only the current thread will have access to `value`.
100 ///
101 /// Because this method guarantees that this `value` will not be observed on any other threads (except through the `*_unlocked` methods), `T` does not need to be `Send` or `Sync`.
102 pub fn new(value: T) -> Self {
103 Self { thread: current().id(), value }
104 }
105
106 /// Constructs a new `ThreadLock`, locked to the given thread; that is, only the given thread will have access to `value`.
107 ///
108 /// Because this method can be used to move or share `value` to another thread, `T` must be `Send` and `Sync`.
109 pub const fn on_thread(value: T, thread: ThreadId) -> Self where T: Send + Sync {
110 Self { thread, value }
111 }
112
113 /// Deconstructs this `ThreadLock` and returns the contained value.
114 ///
115 /// # Errors
116 ///
117 /// If `self` is locked to a different thread; the error object contains `self`, so that it can be recovered if that necessary.
118 pub fn try_into_inner(self) -> Result<T, TryIntoWrongThreadError<Self>> {
119 if self.can_current_thread_access() {
120 Ok(self.value)
121 } else {
122 Err(TryIntoWrongThreadError(self))
123 }
124 }
125
126 /// Deconstructs this `ThreadLock` and returns the contained value.
127 /// Equivalent to `self.try_into_inner().unwrap()`.
128 ///
129 /// # Panics
130 ///
131 /// If `self` is locked to a different thread.
132 pub fn into_inner(self) -> T {
133 self.try_into_inner().unwrap()
134 }
135
136 /// Deconstructs this `ThreadLock` and returns the contained value.
137 ///
138 /// This methods circumvents the thread lock;
139 /// that is, it allows any thread access to the underlying value, provided that that thread can prove that it is safe to move ([`Send`]) that type to other threads
140 /// (as that is essentially what will happen if the thread that calls this method is not the one to which this value is locked).
141 pub fn into_inner_unlocked(self) -> T where T: Send {
142 self.value
143 }
144
145}
146
147impl<T: ?Sized> ThreadLock<T> {
148
149 /// Returns the id of the thread to which this value is locked.
150 pub fn thread(&self) -> ThreadId {
151 self.thread
152 }
153 /// Returns whether this value is locked to the current thread.
154 #[inline]
155 pub fn can_current_thread_access(&self) -> bool {
156 self.thread == current().id()
157 }
158
159 /// Returns a shared reference to the underlying data.
160 ///
161 /// # Errors
162 ///
163 /// If the current thread does not have access to the underlying data.
164 pub fn try_get(&self) -> Result<&T, WrongThreadError> {
165 if self.can_current_thread_access() {
166 Ok(&self.value)
167 } else {
168 Err(WrongThreadError)
169 }
170 }
171
172 /// Returns a unique (mutable) reference to the underlying data.
173 ///
174 /// # Errors
175 ///
176 /// If the current thread does not have access to the underlying data.
177 pub fn try_get_mut(&mut self) -> Result<&mut T, WrongThreadError> {
178 if self.can_current_thread_access() {
179 Ok(&mut self.value)
180 } else {
181 Err(WrongThreadError)
182 }
183 }
184
185 /// Returns a shared reference to the underlying data.
186 /// Equivalent to `self.try_get().unwrap()`
187 ///
188 /// # Panics
189 ///
190 /// If the current thread does not have access to the underlying data.
191 pub fn get(&self) -> &T {
192 self.try_get().unwrap()
193 }
194
195 /// Returns a unique (mutable) reference to the underlying data.
196 /// Equivalent to `self.try_get_mut().unwrap()`
197 ///
198 /// # Panics
199 ///
200 /// If the current thread does not have access to the underlying data.
201 pub fn get_mut(&mut self) -> &mut T {
202 self.try_get_mut().unwrap()
203 }
204
205 /// Returns a shared reference to the underlying data.
206 ///
207 /// This methods circumvents the thread lock;
208 /// that is, it allows any thread access to the underlying value, provided that that thread can prove that it is safe to share ([`Sync`]) that type with other threads
209 /// (as that is essentially what will happen if the thread that calls this method is not the one to which this value is locked).
210 ///
211 /// Note that this method also requires that the value be [`Send`]; I'm not sure if this is necessary:
212 /// if `T: Sync + Copy`, it can be copied onto other threads, and I'm not sure if that falls under the contract of [`Sync`], so I'm erring on the safe side.
213 /// If you happen to know whether the [`Send`] bound is necessary, I would appreciate it if you would open an issue to let me know.
214 pub fn get_unlocked(&self) -> &T where T: Send + Sync {
215 &self.value
216 }
217
218 /// Returns a unique (mutable) reference to the underlying data.
219 ///
220 /// This methods circumvents the thread lock;
221 /// that is, it allows any thread access to the underlying value, provided that that thread can prove that it is safe to move ([`Send`]) that type to other threads
222 /// (as that is essentially what will happen if the thread that calls this method is not the one to which this value is locked).
223 ///
224 /// A note on the [`Send`] bound: `T` must be [`Send`] because there are ways to move data out of it, e.g. via [std::mem::replace];
225 /// however, it does not need to be [`Sync`] because it takes `self` by mutable reference, which guarantees that the current thread has unique access to this `ThreadLock`, and therefore to the data contained in it.
226 pub fn get_unlocked_mut(&mut self) -> &mut T where T: Send {
227 &mut self.value
228 }
229
230}
231
232impl<T: Default> Default for ThreadLock<T> {
233
234 /// Constructs a new `ThreadLock` with the default value of `T`, locked to the current thread; cf. [`ThreadLock::new`].
235 fn default() -> Self {
236 Self::new(T::default())
237 }
238
239}
240
241impl<T: Debug> Debug for ThreadLock<T> {
242
243 /// This method will only display the underlying data if it is called from the thread to which the data is locked, so it can be called from any thread.
244 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
245 struct LockedToAnotherThread;
246 impl Debug for LockedToAnotherThread {
247
248 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
249 write!(f, "<locked to another thread>")
250 }
251
252 }
253 f
254 .debug_struct("ThreadLock")
255 .field("thread", &self.thread)
256 .field("value", self.try_get().map_or(&LockedToAnotherThread, |value| value)) // still using the closure so that &T gets coerced to &dyn Debug
257 .finish()
258 }
259
260}
261
262/// SAFETY: the value of `T` can only be accessed on a single thread, regardless of which thread actually owns this lock;
263/// thus, it can be safely moved around even if `T` is not [`Send`], because the `T` will never be observed on any other thread
264/// (except through the `*_unlocked` methods, which do have appropriate [`Send`] and [`Sync`] bounds).
265unsafe impl<T: ?Sized> Send for ThreadLock<T> {}
266
267/// SAFETY: the value of `T` can only be accessed on a single thread, regardless of which thread owns this lock;
268/// thus, it can be safely shared even if `T` is not [`Sync`], because the `T` will never be observed on any other thread
269/// (except through the `*_unlocked` methods, which do have appropriate [`Send`] and [`Sync`] bounds).
270unsafe impl<T: ?Sized> Sync for ThreadLock<T> {}
271
272/// An error returned by [`ThreadLock::try_get`] and [`ThreadLock::try_get_mut`].
273#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Default)]
274pub struct WrongThreadError;
275
276impl Display for WrongThreadError {
277
278 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
279 write!(f, "a thread-locked object was accessed on the wrong thread")
280 }
281
282}
283
284impl Error for WrongThreadError {}
285
286/// An error returned by [`ThreadLock::try_into_inner`].
287///
288/// Spiritually equivalent to [`WrongThreadError`], but with a data field to return the [`ThreadLock`] that would otherwise be lost if a call to [`ThreadLock::try_into_inner`] failed.
289#[derive(Copy, Clone, Hash, Eq, PartialEq, Default)]
290pub struct TryIntoWrongThreadError<T>(pub T);
291
292impl<T> Debug for TryIntoWrongThreadError<T> {
293
294 /// Note that this method does not include any info from `self.0` so as to avoid requiring `T: Debug`.
295 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
296 f.debug_tuple("TryIntoWrongThreadError").finish()
297 }
298
299}
300
301impl<T> Display for TryIntoWrongThreadError<T> {
302
303 /// Note that this method does not include any info from `self.0` so as to avoid requiring `T: Display`.
304 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
305 write!(f, "a thread-locked object was accessed on the wrong thread")
306 }
307
308}
309
310impl<T> Error for TryIntoWrongThreadError<T> {}
311
312impl<T> From<TryIntoWrongThreadError<T>> for WrongThreadError {
313
314 fn from(_: TryIntoWrongThreadError<T>) -> Self {
315 Self
316 }
317
318}