Skip to main content

nautilus_core/
shared.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Wrappers around shared, interior-mutable cell pairs.
17//!
18//! NautilusTrader engines store many components as `Rc<RefCell<T>>` for shared ownership with
19//! interior mutability. Spelling that type at every boundary is verbose and risks accidentally
20//! holding a strong reference where a weak one is required, leading to reference cycles.
21//!
22//! [`SharedCell<T>`] and [`WeakCell<T>`] are zero-cost newtypes that name the intent and forward
23//! the common operations (`new`, `borrow`, `borrow_mut`, `with`, `with_mut`, `downgrade`,
24//! `upgrade`). They are `#[repr(transparent)]` and share the memory layout of the wrapped `Rc` /
25//! `Weak`.
26//!
27//! ## Choosing between `SharedCell` and `WeakCell`
28//!
29//! - Use [`SharedCell<T>`] when the holder owns or co-owns the value, like a plain
30//!   `Rc<RefCell<T>>`.
31//! - Use [`WeakCell<T>`] for back-references that would otherwise form a cycle. The back-pointer
32//!   does not keep the value alive; every access must first `upgrade()` to a strong
33//!   [`SharedCell`]. This pattern breaks circular ownership: for an `Exchange` that owns an
34//!   `ExecutionClient` which references the exchange, the exchange holds a [`SharedCell`] to the
35//!   client and the client holds a [`WeakCell`] back to the exchange.
36
37use std::{
38    cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut},
39    hash::{Hash, Hasher},
40    rc::{Rc, Weak},
41};
42
43/// Strong, shared ownership of `T` with interior mutability.
44#[repr(transparent)]
45#[derive(Debug)]
46pub struct SharedCell<T>(Rc<RefCell<T>>);
47
48impl<T> Clone for SharedCell<T> {
49    fn clone(&self) -> Self {
50        Self(self.0.clone())
51    }
52}
53
54impl<T> SharedCell<T> {
55    /// Wraps a value inside `Rc<RefCell<..>>`.
56    #[inline]
57    pub fn new(value: T) -> Self {
58        Self(Rc::new(RefCell::new(value)))
59    }
60
61    /// Creates a [`WeakCell`] pointing to the same allocation.
62    #[inline]
63    #[must_use]
64    pub fn downgrade(&self) -> WeakCell<T> {
65        WeakCell(Rc::downgrade(&self.0))
66    }
67
68    /// Immutable borrow of the inner value.
69    #[inline]
70    #[must_use]
71    pub fn borrow(&self) -> Ref<'_, T> {
72        self.0.borrow()
73    }
74
75    /// Mutable borrow of the inner value.
76    #[inline]
77    #[must_use]
78    pub fn borrow_mut(&self) -> RefMut<'_, T> {
79        self.0.borrow_mut()
80    }
81
82    /// Attempts to immutably borrow the inner value.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`BorrowError`] if the value is currently mutably borrowed.
87    #[inline]
88    pub fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError> {
89        self.0.try_borrow()
90    }
91
92    /// Attempts to mutably borrow the inner value.
93    ///
94    /// # Errors
95    ///
96    /// Returns [`BorrowMutError`] if the value is currently borrowed
97    /// (mutably or immutably).
98    #[inline]
99    pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>, BorrowMutError> {
100        self.0.try_borrow_mut()
101    }
102
103    /// Number of active strong references.
104    #[inline]
105    #[must_use]
106    pub fn strong_count(&self) -> usize {
107        Rc::strong_count(&self.0)
108    }
109
110    /// Number of active weak references.
111    #[inline]
112    #[must_use]
113    pub fn weak_count(&self) -> usize {
114        Rc::weak_count(&self.0)
115    }
116
117    /// Returns the raw pointer to the underlying cell, useful for identity diagnostics.
118    #[inline]
119    #[must_use]
120    pub fn as_ptr(&self) -> *const RefCell<T> {
121        Rc::as_ptr(&self.0)
122    }
123
124    /// Runs `f` against an immutable borrow of the inner value, returning its result.
125    ///
126    /// The borrow is dropped at the end of the closure, so callers can safely follow
127    /// the call with operations that re-enter the same cell (e.g. event dispatch).
128    #[inline]
129    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
130        f(&self.0.borrow())
131    }
132
133    /// Runs `f` against a mutable borrow of the inner value, returning its result.
134    ///
135    /// The borrow is dropped at the end of the closure, so callers can safely follow
136    /// the call with operations that re-enter the same cell.
137    #[inline]
138    pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
139        f(&mut self.0.borrow_mut())
140    }
141}
142
143impl<T> PartialEq for SharedCell<T> {
144    /// Identity equality: two handles compare equal when they point to the same cell.
145    fn eq(&self, other: &Self) -> bool {
146        Rc::ptr_eq(&self.0, &other.0)
147    }
148}
149
150impl<T> Eq for SharedCell<T> {}
151
152impl<T> Hash for SharedCell<T> {
153    /// Hashes the cell's pointer address, consistent with the identity-based [`PartialEq`] impl.
154    fn hash<H: Hasher>(&self, state: &mut H) {
155        Rc::as_ptr(&self.0).hash(state);
156    }
157}
158
159impl<T> From<Rc<RefCell<T>>> for SharedCell<T> {
160    fn from(inner: Rc<RefCell<T>>) -> Self {
161        Self(inner)
162    }
163}
164
165impl<T> From<SharedCell<T>> for Rc<RefCell<T>> {
166    fn from(shared: SharedCell<T>) -> Self {
167        shared.0
168    }
169}
170
171impl<T> std::ops::Deref for SharedCell<T> {
172    type Target = Rc<RefCell<T>>;
173
174    fn deref(&self) -> &Self::Target {
175        &self.0
176    }
177}
178
179/// Weak counterpart to [`SharedCell`].
180#[repr(transparent)]
181#[derive(Debug)]
182pub struct WeakCell<T>(Weak<RefCell<T>>);
183
184impl<T> Clone for WeakCell<T> {
185    fn clone(&self) -> Self {
186        Self(self.0.clone())
187    }
188}
189
190impl<T> WeakCell<T> {
191    /// Attempts to upgrade the weak reference to a strong [`SharedCell`].
192    #[inline]
193    pub fn upgrade(&self) -> Option<SharedCell<T>> {
194        self.0.upgrade().map(SharedCell)
195    }
196
197    /// Returns `true` if the pointed-to value has been dropped.
198    #[inline]
199    #[must_use]
200    pub fn is_dropped(&self) -> bool {
201        self.0.strong_count() == 0
202    }
203}
204
205impl<T> From<Weak<RefCell<T>>> for WeakCell<T> {
206    fn from(inner: Weak<RefCell<T>>) -> Self {
207        Self(inner)
208    }
209}
210
211impl<T> From<WeakCell<T>> for Weak<RefCell<T>> {
212    fn from(cell: WeakCell<T>) -> Self {
213        cell.0
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use std::collections::HashSet;
220
221    use rstest::rstest;
222
223    use super::*;
224
225    #[rstest]
226    fn test_shared_cell_new_and_borrow() {
227        let cell = SharedCell::new(42);
228        assert_eq!(*cell.borrow(), 42);
229    }
230
231    #[rstest]
232    fn test_shared_cell_borrow_mut() {
233        let cell = SharedCell::new(0);
234        *cell.borrow_mut() = 99;
235        assert_eq!(*cell.borrow(), 99);
236    }
237
238    #[rstest]
239    fn test_shared_cell_clone_shares_value() {
240        let cell = SharedCell::new(10);
241        let clone = cell.clone();
242        *cell.borrow_mut() = 20;
243        assert_eq!(*clone.borrow(), 20);
244    }
245
246    #[rstest]
247    fn test_shared_cell_strong_weak_counts() {
248        let cell = SharedCell::new(1);
249        assert_eq!(cell.strong_count(), 1);
250        assert_eq!(cell.weak_count(), 0);
251
252        let weak = cell.downgrade();
253        assert_eq!(cell.weak_count(), 1);
254        assert_eq!(cell.strong_count(), 1);
255
256        let clone = cell.clone();
257        assert_eq!(cell.strong_count(), 2);
258        drop(clone);
259        assert_eq!(cell.strong_count(), 1);
260        drop(weak);
261        assert_eq!(cell.weak_count(), 0);
262    }
263
264    #[rstest]
265    fn test_weak_cell_upgrade_succeeds_while_alive() {
266        let cell = SharedCell::new(10);
267        let weak = cell.downgrade();
268        assert!(!weak.is_dropped());
269
270        let upgraded = weak.upgrade();
271        assert!(upgraded.is_some());
272        assert_eq!(*upgraded.unwrap().borrow(), 10);
273    }
274
275    #[rstest]
276    fn test_weak_cell_upgrade_fails_after_drop() {
277        let weak = {
278            let cell = SharedCell::new(10);
279            cell.downgrade()
280        };
281        assert!(weak.is_dropped());
282        assert!(weak.upgrade().is_none());
283    }
284
285    #[rstest]
286    #[expect(clippy::redundant_clone, reason = "Clone is the behavior under test")]
287    fn test_weak_cell_clone() {
288        let cell = SharedCell::new(5);
289        let weak1 = cell.downgrade();
290        let weak2 = weak1.clone();
291        assert_eq!(cell.weak_count(), 2);
292        assert_eq!(*weak2.upgrade().unwrap().borrow(), 5);
293    }
294
295    #[rstest]
296    fn test_try_borrow_fails_while_mutably_borrowed() {
297        let cell = SharedCell::new(0);
298        let _guard = cell.borrow_mut();
299        assert!(cell.try_borrow().is_err());
300    }
301
302    #[rstest]
303    fn test_try_borrow_mut_fails_while_borrowed() {
304        let cell = SharedCell::new(0);
305        let _guard = cell.borrow();
306        assert!(cell.try_borrow_mut().is_err());
307    }
308
309    #[rstest]
310    fn test_from_rc_refcell_roundtrip() {
311        let rc = Rc::new(RefCell::new(5));
312        let cell = SharedCell::from(rc);
313        assert_eq!(*cell.borrow(), 5);
314
315        let back: Rc<RefCell<i32>> = cell.into();
316        assert_eq!(*back.borrow(), 5);
317    }
318
319    #[rstest]
320    fn test_from_weak_refcell_roundtrip() {
321        let rc = Rc::new(RefCell::new(7));
322        let weak_cell = WeakCell::from(Rc::downgrade(&rc));
323        assert_eq!(*weak_cell.upgrade().unwrap().borrow(), 7);
324
325        let back: Weak<RefCell<i32>> = weak_cell.into();
326        assert_eq!(*back.upgrade().unwrap().borrow(), 7);
327    }
328
329    #[rstest]
330    fn test_partial_eq_is_pointer_identity() {
331        let a = SharedCell::new(10);
332        let b = a.clone();
333        let c = SharedCell::new(10);
334
335        assert_eq!(a, b);
336        assert_ne!(a, c);
337    }
338
339    #[rstest]
340    #[expect(
341        clippy::mutable_key_type,
342        reason = "SharedCell hashes by pointer identity, not interior value"
343    )]
344    fn test_hash_matches_pointer_identity() {
345        let a = SharedCell::new(10);
346        let b = a.clone();
347        let c = SharedCell::new(10);
348
349        let mut set: HashSet<SharedCell<i32>> = HashSet::new();
350        set.insert(a);
351        assert!(set.contains(&b));
352        assert!(!set.contains(&c));
353    }
354
355    #[rstest]
356    fn test_as_ptr_matches_clone() {
357        let cell = SharedCell::new(0);
358        let cloned = cell.clone();
359        assert_eq!(cell.as_ptr(), cloned.as_ptr());
360    }
361
362    #[rstest]
363    fn test_with_drops_borrow_before_returning() {
364        let cell = SharedCell::new(100);
365        let value = cell.with(|v| *v);
366
367        // Borrow released; subsequent borrow_mut works without panic.
368        *cell.borrow_mut() = 1;
369
370        assert_eq!(value, 100);
371        assert_eq!(*cell.borrow(), 1);
372    }
373
374    #[rstest]
375    fn test_with_mut_drops_borrow_before_returning() {
376        let cell = SharedCell::new(0);
377        let returned = cell.with_mut(|v| {
378            *v = 7;
379            *v
380        });
381
382        // Borrow released; we can read back through a fresh borrow.
383        assert_eq!(returned, 7);
384        assert_eq!(*cell.borrow(), 7);
385    }
386}