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
37#![warn(clippy::clone_on_ref_ptr)]
38
39use std::{
40    cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut},
41    hash::{Hash, Hasher},
42    rc::{Rc, Weak},
43};
44
45/// Strong, shared ownership of `T` with interior mutability.
46#[repr(transparent)]
47#[derive(Debug)]
48pub struct SharedCell<T>(Rc<RefCell<T>>);
49
50impl<T> Clone for SharedCell<T> {
51    fn clone(&self) -> Self {
52        Self(Rc::clone(&self.0))
53    }
54}
55
56impl<T> SharedCell<T> {
57    /// Wraps a value inside `Rc<RefCell<..>>`.
58    #[inline]
59    pub fn new(value: T) -> Self {
60        Self(Rc::new(RefCell::new(value)))
61    }
62
63    /// Creates a [`WeakCell`] pointing to the same allocation.
64    #[inline]
65    #[must_use]
66    pub fn downgrade(&self) -> WeakCell<T> {
67        WeakCell(Rc::downgrade(&self.0))
68    }
69
70    /// Immutable borrow of the inner value.
71    #[inline]
72    #[must_use]
73    pub fn borrow(&self) -> Ref<'_, T> {
74        self.0.borrow()
75    }
76
77    /// Mutable borrow of the inner value.
78    #[inline]
79    #[must_use]
80    pub fn borrow_mut(&self) -> RefMut<'_, T> {
81        self.0.borrow_mut()
82    }
83
84    /// Attempts to immutably borrow the inner value.
85    ///
86    /// # Errors
87    ///
88    /// Returns an error if the value is currently mutably borrowed.
89    #[inline]
90    pub fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError> {
91        self.0.try_borrow()
92    }
93
94    /// Attempts to mutably borrow the inner value.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if the value is currently borrowed (mutably or immutably).
99    #[inline]
100    pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>, BorrowMutError> {
101        self.0.try_borrow_mut()
102    }
103
104    /// Number of active strong references.
105    #[inline]
106    #[must_use]
107    pub fn strong_count(&self) -> usize {
108        Rc::strong_count(&self.0)
109    }
110
111    /// Number of active weak references.
112    #[inline]
113    #[must_use]
114    pub fn weak_count(&self) -> usize {
115        Rc::weak_count(&self.0)
116    }
117
118    /// Returns the raw pointer to the underlying cell, useful for identity diagnostics.
119    #[inline]
120    #[must_use]
121    pub fn as_ptr(&self) -> *const RefCell<T> {
122        Rc::as_ptr(&self.0)
123    }
124
125    /// Runs `f` against an immutable borrow of the inner value, returning its result.
126    ///
127    /// The borrow is dropped at the end of the closure, so callers can safely follow
128    /// the call with operations that re-enter the same cell (e.g. event dispatch).
129    #[inline]
130    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
131        f(&self.0.borrow())
132    }
133
134    /// Runs `f` against a mutable borrow of the inner value, returning its result.
135    ///
136    /// The borrow is dropped at the end of the closure, so callers can safely follow
137    /// the call with operations that re-enter the same cell.
138    #[inline]
139    pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
140        f(&mut self.0.borrow_mut())
141    }
142}
143
144impl<T> PartialEq for SharedCell<T> {
145    /// Identity equality: two handles compare equal when they point to the same cell.
146    fn eq(&self, other: &Self) -> bool {
147        Rc::ptr_eq(&self.0, &other.0)
148    }
149}
150
151impl<T> Eq for SharedCell<T> {}
152
153impl<T> Hash for SharedCell<T> {
154    /// Hashes the cell's pointer address, consistent with the identity-based [`PartialEq`] impl.
155    fn hash<H: Hasher>(&self, state: &mut H) {
156        Rc::as_ptr(&self.0).hash(state);
157    }
158}
159
160impl<T> From<Rc<RefCell<T>>> for SharedCell<T> {
161    fn from(inner: Rc<RefCell<T>>) -> Self {
162        Self(inner)
163    }
164}
165
166impl<T> From<SharedCell<T>> for Rc<RefCell<T>> {
167    fn from(shared: SharedCell<T>) -> Self {
168        shared.0
169    }
170}
171
172impl<T> std::ops::Deref for SharedCell<T> {
173    type Target = Rc<RefCell<T>>;
174
175    fn deref(&self) -> &Self::Target {
176        &self.0
177    }
178}
179
180/// Weak counterpart to [`SharedCell`].
181#[repr(transparent)]
182#[derive(Debug)]
183pub struct WeakCell<T>(Weak<RefCell<T>>);
184
185impl<T> Clone for WeakCell<T> {
186    fn clone(&self) -> Self {
187        Self(Weak::clone(&self.0))
188    }
189}
190
191impl<T> WeakCell<T> {
192    /// Attempts to upgrade the weak reference to a strong [`SharedCell`].
193    #[inline]
194    pub fn upgrade(&self) -> Option<SharedCell<T>> {
195        self.0.upgrade().map(SharedCell)
196    }
197
198    /// Returns `true` if the pointed-to value has been dropped.
199    #[inline]
200    #[must_use]
201    pub fn is_dropped(&self) -> bool {
202        self.0.strong_count() == 0
203    }
204}
205
206impl<T> From<Weak<RefCell<T>>> for WeakCell<T> {
207    fn from(inner: Weak<RefCell<T>>) -> Self {
208        Self(inner)
209    }
210}
211
212impl<T> From<WeakCell<T>> for Weak<RefCell<T>> {
213    fn from(cell: WeakCell<T>) -> Self {
214        cell.0
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use std::collections::HashSet;
221
222    use rstest::rstest;
223
224    use super::*;
225
226    #[rstest]
227    fn test_shared_cell_new_and_borrow() {
228        let cell = SharedCell::new(42);
229        assert_eq!(*cell.borrow(), 42);
230    }
231
232    #[rstest]
233    fn test_shared_cell_borrow_mut() {
234        let cell = SharedCell::new(0);
235        *cell.borrow_mut() = 99;
236        assert_eq!(*cell.borrow(), 99);
237    }
238
239    #[rstest]
240    fn test_shared_cell_clone_shares_value() {
241        let cell = SharedCell::new(10);
242        let clone = SharedCell::clone(&cell);
243        *cell.borrow_mut() = 20;
244        assert_eq!(*clone.borrow(), 20);
245    }
246
247    #[rstest]
248    fn test_shared_cell_strong_weak_counts() {
249        let cell = SharedCell::new(1);
250        assert_eq!(cell.strong_count(), 1);
251        assert_eq!(cell.weak_count(), 0);
252
253        let weak = cell.downgrade();
254        assert_eq!(cell.weak_count(), 1);
255        assert_eq!(cell.strong_count(), 1);
256
257        let clone = SharedCell::clone(&cell);
258        assert_eq!(cell.strong_count(), 2);
259        drop(clone);
260        assert_eq!(cell.strong_count(), 1);
261        drop(weak);
262        assert_eq!(cell.weak_count(), 0);
263    }
264
265    #[rstest]
266    fn test_weak_cell_upgrade_succeeds_while_alive() {
267        let cell = SharedCell::new(10);
268        let weak = cell.downgrade();
269        assert!(!weak.is_dropped());
270
271        let upgraded = weak.upgrade();
272        assert!(upgraded.is_some());
273        assert_eq!(*upgraded.unwrap().borrow(), 10);
274    }
275
276    #[rstest]
277    fn test_weak_cell_upgrade_fails_after_drop() {
278        let weak = {
279            let cell = SharedCell::new(10);
280            cell.downgrade()
281        };
282        assert!(weak.is_dropped());
283        assert!(weak.upgrade().is_none());
284    }
285
286    #[rstest]
287    #[expect(clippy::redundant_clone, reason = "Clone is the behavior under test")]
288    fn test_weak_cell_clone() {
289        let cell = SharedCell::new(5);
290        let weak1 = cell.downgrade();
291        let weak2 = weak1.clone();
292        assert_eq!(cell.weak_count(), 2);
293        assert_eq!(*weak2.upgrade().unwrap().borrow(), 5);
294    }
295
296    #[rstest]
297    fn test_try_borrow_fails_while_mutably_borrowed() {
298        let cell = SharedCell::new(0);
299        let _guard = cell.borrow_mut();
300        assert!(cell.try_borrow().is_err());
301    }
302
303    #[rstest]
304    fn test_try_borrow_mut_fails_while_borrowed() {
305        let cell = SharedCell::new(0);
306        let _guard = cell.borrow();
307        assert!(cell.try_borrow_mut().is_err());
308    }
309
310    #[rstest]
311    fn test_from_rc_refcell_roundtrip() {
312        let rc = Rc::new(RefCell::new(5));
313        let cell = SharedCell::from(rc);
314        assert_eq!(*cell.borrow(), 5);
315
316        let back: Rc<RefCell<i32>> = cell.into();
317        assert_eq!(*back.borrow(), 5);
318    }
319
320    #[rstest]
321    fn test_from_weak_refcell_roundtrip() {
322        let rc = Rc::new(RefCell::new(7));
323        let weak_cell = WeakCell::from(Rc::downgrade(&rc));
324        assert_eq!(*weak_cell.upgrade().unwrap().borrow(), 7);
325
326        let back: Weak<RefCell<i32>> = weak_cell.into();
327        assert_eq!(*back.upgrade().unwrap().borrow(), 7);
328    }
329
330    #[rstest]
331    fn test_partial_eq_is_pointer_identity() {
332        let a = SharedCell::new(10);
333        let b = SharedCell::clone(&a);
334        let c = SharedCell::new(10);
335
336        assert_eq!(a, b);
337        assert_ne!(a, c);
338    }
339
340    #[rstest]
341    #[expect(
342        clippy::mutable_key_type,
343        reason = "SharedCell hashes by pointer identity, not interior value"
344    )]
345    fn test_hash_matches_pointer_identity() {
346        let a = SharedCell::new(10);
347        let b = SharedCell::clone(&a);
348        let c = SharedCell::new(10);
349
350        let mut set: HashSet<SharedCell<i32>> = HashSet::new();
351        set.insert(a);
352        assert!(set.contains(&b));
353        assert!(!set.contains(&c));
354    }
355
356    #[rstest]
357    fn test_as_ptr_matches_clone() {
358        let cell = SharedCell::new(0);
359        let cloned = SharedCell::clone(&cell);
360        assert_eq!(cell.as_ptr(), cloned.as_ptr());
361    }
362
363    #[rstest]
364    fn test_with_drops_borrow_before_returning() {
365        let cell = SharedCell::new(100);
366        let value = cell.with(|v| *v);
367
368        // Borrow released; subsequent borrow_mut works without panic.
369        *cell.borrow_mut() = 1;
370
371        assert_eq!(value, 100);
372        assert_eq!(*cell.borrow(), 1);
373    }
374
375    #[rstest]
376    fn test_with_mut_drops_borrow_before_returning() {
377        let cell = SharedCell::new(0);
378        let returned = cell.with_mut(|v| {
379            *v = 7;
380            *v
381        });
382
383        // Borrow released; we can read back through a fresh borrow.
384        assert_eq!(returned, 7);
385        assert_eq!(*cell.borrow(), 7);
386    }
387}