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}