toad_stem/
lib.rs

1//! This microcrate provides a mutable memory wrapper that is thread-safe
2//! and usable on `no_std` platforms by using [`std::sync::RwLock`]
3//! when crate feature `std` is enabled (this is the default) and
4//! falling back to [`core::cell::Cell`] when `std` disabled.
5//!
6//! the API of the core struct [`Stem`] was chosen to discourage long-lived
7//! immutable references to the cell's contents, so that deadlocks are less likely.
8
9// docs
10#![doc(html_root_url = "https://docs.rs/toad-stem/0.0.0")]
11#![cfg_attr(any(docsrs, feature = "docs"), feature(doc_cfg))]
12// -
13// style
14#![allow(clippy::unused_unit)]
15// -
16// deny
17#![deny(missing_docs)]
18#![deny(missing_debug_implementations)]
19#![deny(missing_copy_implementations)]
20#![cfg_attr(not(test), deny(unsafe_code))]
21// -
22// warnings
23#![cfg_attr(not(test), warn(unreachable_pub))]
24// -
25// features
26#![cfg_attr(not(feature = "std"), no_std)]
27
28#[cfg(feature = "alloc")]
29extern crate alloc as std_alloc;
30
31use core::ops::{Deref, DerefMut};
32
33#[cfg(feature = "std")]
34type Inner<T> = std::sync::RwLock<T>;
35
36#[cfg(not(feature = "std"))]
37type Inner<T> = core::cell::RefCell<T>;
38
39/// A thread-safe mutable memory location that allows
40/// for many concurrent readers or a single writer.
41///
42/// When feature `std` enabled, this uses [`std::sync::RwLock`].
43/// When `std` disabled, uses [`core::cell::Cell`].
44#[derive(Debug, Default)]
45pub struct Stem<T>(Inner<T>);
46
47impl<T> Stem<T> {
48  /// Create a new Stem cell
49  pub const fn new(t: T) -> Self {
50    Self(Inner::new(t))
51  }
52
53  /// Map a reference to `T` to a new type
54  ///
55  /// This will block if called concurrently with `map_mut`.
56  ///
57  /// There can be any number of concurrent `map_ref`
58  /// sections running at a given time.
59  pub fn map_ref<F, R>(&self, f: F) -> R
60    where F: for<'a> FnMut(&'a T) -> R
61  {
62    self.0.map_ref(f)
63  }
64
65  /// Map a mutable reference to `T` to a new type
66  ///
67  /// This will block if called concurrently with `map_ref` or `map_mut`.
68  pub fn map_mut<F, R>(&self, f: F) -> R
69    where F: for<'a> FnMut(&'a mut T) -> R
70  {
71    self.0.map_mut(f)
72  }
73}
74
75// NOTE(orion): I chose to use a trait here to tie RwLock
76// and Cell together in a testable way, to keep the actual
77// code behind feature flags extremely thin.
78
79/// A mutable memory location
80///
81/// This is used to back the behavior of [`Stem`],
82/// which should be used instead of this trait.
83pub trait StemCellInternal<T> {
84  /// Create an instance of `Self`
85  fn new(t: T) -> Self
86    where Self: Sized;
87
88  /// Map a reference to `T` to a new type
89  ///
90  /// Implementors may choose to panic or block
91  /// if `map_mut` called concurrently.
92  fn map_ref<F, R>(&self, f: F) -> R
93    where F: for<'a> FnMut(&'a T) -> R;
94
95  /// Map a mutable reference to `T` to a new type
96  ///
97  /// Implementors may choose to panic or block
98  /// if `map_ref` or `map_mut` called concurrently.
99  fn map_mut<F, R>(&self, f: F) -> R
100    where F: for<'a> FnMut(&'a mut T) -> R;
101}
102
103#[cfg(feature = "std")]
104impl<T> StemCellInternal<T> for std::sync::RwLock<T> {
105  fn new(t: T) -> Self {
106    Self::new(t)
107  }
108
109  fn map_ref<F, R>(&self, mut f: F) -> R
110    where F: for<'a> FnMut(&'a T) -> R
111  {
112    f(self.read().unwrap().deref())
113  }
114
115  fn map_mut<F, R>(&self, mut f: F) -> R
116    where F: for<'a> FnMut(&'a mut T) -> R
117  {
118    f(self.write().unwrap().deref_mut())
119  }
120}
121
122impl<T> StemCellInternal<T> for core::cell::RefCell<T> {
123  fn new(t: T) -> Self {
124    Self::new(t)
125  }
126
127  fn map_ref<F, R>(&self, mut f: F) -> R
128    where F: for<'a> FnMut(&'a T) -> R
129  {
130    f(self.borrow().deref())
131  }
132
133  fn map_mut<F, R>(&self, mut f: F) -> R
134    where F: for<'a> FnMut(&'a mut T) -> R
135  {
136    f(self.borrow_mut().deref_mut())
137  }
138}
139
140#[cfg(test)]
141mod test {
142  use core::cell::RefCell;
143  use std::sync::{Arc, Barrier, RwLock};
144
145  use super::*;
146
147  #[test]
148  fn refcell_mut() {
149    let s = RefCell::new(Vec::<usize>::new());
150    s.map_mut(|v| v.push(12));
151    s.map_ref(|v| assert_eq!(v, &vec![12usize]));
152  }
153
154  #[test]
155  fn refcell_concurrent_read_does_not_block_or_panic() {
156    let s = RefCell::new(Vec::<usize>::new());
157    s.map_ref(|_| s.map_ref(|_| ()));
158  }
159
160  #[test]
161  fn rwlock_mut() {
162    let s = RwLock::new(Vec::<usize>::new());
163    s.map_mut(|v| v.push(12));
164    s.map_ref(|v| assert_eq!(v, &vec![12usize]));
165  }
166
167  #[test]
168  fn rwlock_concurrent_read_does_not_block_or_panic() {
169    let s = RwLock::new(Vec::<usize>::new());
170    s.map_ref(|_| s.map_ref(|_| ()));
171  }
172
173  #[test]
174  fn stem_modify_blocks_until_refs_dropped() {
175    unsafe {
176      static VEC: Stem<Vec<usize>> = Stem::new(Vec::new());
177
178      static mut START: Option<Arc<Barrier>> = None;
179      static mut READING: Option<Arc<Barrier>> = None;
180      static mut READING_DONE: Option<Arc<Barrier>> = None;
181      static mut MODIFY_DONE: Option<Arc<Barrier>> = None;
182
183      START = Some(Arc::new(Barrier::new(3)));
184      READING = Some(Arc::new(Barrier::new(3)));
185      READING_DONE = Some(Arc::new(Barrier::new(2)));
186      MODIFY_DONE = Some(Arc::new(Barrier::new(3)));
187
188      macro_rules! wait {
189        ($b:ident) => {
190          $b.as_ref().unwrap().clone().wait();
191        };
192      }
193
194      std::thread::spawn(|| {
195        wait!(START);
196        VEC.map_ref(|v| {
197             assert!(v.is_empty());
198             wait!(READING);
199             wait!(READING_DONE);
200           });
201
202        wait!(MODIFY_DONE);
203      });
204
205      std::thread::spawn(|| {
206        wait!(START);
207        wait!(READING);
208        VEC.map_mut(|v| v.push(12)); // unblocked by READING_DONE
209        wait!(MODIFY_DONE);
210      });
211
212      wait!(START);
213      wait!(READING);
214      wait!(READING_DONE);
215      wait!(MODIFY_DONE);
216      VEC.map_ref(|v| assert_eq!(v, &vec![12]));
217    }
218  }
219}