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//! Efficient and ergonomic wrappers around frequently-used `Rc<RefCell<T>>` / `Weak<RefCell<T>>` pairs.
17//!
18//! The NautilusTrader codebase heavily relies on shared, interior-mutable ownership for many
19//! engine components (`Rc<RefCell<T>>`). Repeating that verbose type across many APIs—alongside
20//! its weak counterpart—clutters code and increases the likelihood of accidentally storing a
21//! strong reference where only a weak reference is required (leading to reference cycles).
22//!
23//! `SharedCell<T>` and `WeakCell<T>` are zero-cost new-types that make the intent explicit and
24//! offer convenience helpers (`downgrade`, `upgrade`, `borrow`, `borrow_mut`). Because the
25//! wrappers are `#[repr(transparent)]`, they have the exact same memory layout as the wrapped
26//! `Rc` / `Weak` and introduce no runtime overhead.
27
28//! ## Choosing between `SharedCell` and `WeakCell`
29//!
30//! * Use **`SharedCell<T>`** when the current owner genuinely *owns* (or co-owns) the value –
31//!   just as you would normally store an `Rc<RefCell<T>>`.
32//! * Use **`WeakCell<T>`** for back-references that could otherwise form a reference cycle.
33//!   The back-pointer does **not** keep the value alive, and every access must first
34//!   `upgrade()` to a strong `SharedCell`. This pattern is how we break circular ownership such
35//!   as *Exchange ↔ `ExecutionClient`*: the exchange keeps a `SharedCell` to the client, while the
36//!   client holds only a `WeakCell` back to the exchange.
37
38use std::{
39    cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut},
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
118impl<T> From<Rc<RefCell<T>>> for SharedCell<T> {
119    fn from(inner: Rc<RefCell<T>>) -> Self {
120        Self(inner)
121    }
122}
123
124impl<T> From<SharedCell<T>> for Rc<RefCell<T>> {
125    fn from(shared: SharedCell<T>) -> Self {
126        shared.0
127    }
128}
129
130impl<T> std::ops::Deref for SharedCell<T> {
131    type Target = Rc<RefCell<T>>;
132
133    fn deref(&self) -> &Self::Target {
134        &self.0
135    }
136}
137
138/// Weak counterpart to [`SharedCell`].
139#[repr(transparent)]
140#[derive(Debug)]
141pub struct WeakCell<T>(Weak<RefCell<T>>);
142
143impl<T> Clone for WeakCell<T> {
144    fn clone(&self) -> Self {
145        Self(self.0.clone())
146    }
147}
148
149impl<T> WeakCell<T> {
150    /// Attempts to upgrade the weak reference to a strong [`SharedCell`].
151    #[inline]
152    pub fn upgrade(&self) -> Option<SharedCell<T>> {
153        self.0.upgrade().map(SharedCell)
154    }
155
156    /// Returns `true` if the pointed-to value has been dropped.
157    #[inline]
158    #[must_use]
159    pub fn is_dropped(&self) -> bool {
160        self.0.strong_count() == 0
161    }
162}
163
164impl<T> From<Weak<RefCell<T>>> for WeakCell<T> {
165    fn from(inner: Weak<RefCell<T>>) -> Self {
166        Self(inner)
167    }
168}
169
170impl<T> From<WeakCell<T>> for Weak<RefCell<T>> {
171    fn from(cell: WeakCell<T>) -> Self {
172        cell.0
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use rstest::rstest;
179
180    use super::*;
181
182    #[rstest]
183    fn test_shared_cell_new_and_borrow() {
184        let cell = SharedCell::new(42);
185        assert_eq!(*cell.borrow(), 42);
186    }
187
188    #[rstest]
189    fn test_shared_cell_borrow_mut() {
190        let cell = SharedCell::new(0);
191        *cell.borrow_mut() = 99;
192        assert_eq!(*cell.borrow(), 99);
193    }
194
195    #[rstest]
196    fn test_shared_cell_clone_shares_value() {
197        let cell = SharedCell::new(10);
198        let clone = cell.clone();
199        *cell.borrow_mut() = 20;
200        assert_eq!(*clone.borrow(), 20);
201    }
202
203    #[rstest]
204    fn test_shared_cell_strong_weak_counts() {
205        let cell = SharedCell::new(1);
206        assert_eq!(cell.strong_count(), 1);
207        assert_eq!(cell.weak_count(), 0);
208
209        let weak = cell.downgrade();
210        assert_eq!(cell.weak_count(), 1);
211        assert_eq!(cell.strong_count(), 1);
212
213        let clone = cell.clone();
214        assert_eq!(cell.strong_count(), 2);
215        drop(clone);
216        assert_eq!(cell.strong_count(), 1);
217        drop(weak);
218        assert_eq!(cell.weak_count(), 0);
219    }
220
221    #[rstest]
222    fn test_weak_cell_upgrade_succeeds_while_alive() {
223        let cell = SharedCell::new(10);
224        let weak = cell.downgrade();
225        assert!(!weak.is_dropped());
226
227        let upgraded = weak.upgrade();
228        assert!(upgraded.is_some());
229        assert_eq!(*upgraded.unwrap().borrow(), 10);
230    }
231
232    #[rstest]
233    fn test_weak_cell_upgrade_fails_after_drop() {
234        let weak = {
235            let cell = SharedCell::new(10);
236            cell.downgrade()
237        };
238        assert!(weak.is_dropped());
239        assert!(weak.upgrade().is_none());
240    }
241
242    #[rstest]
243    #[expect(clippy::redundant_clone, reason = "Clone is the behavior under test")]
244    fn test_weak_cell_clone() {
245        let cell = SharedCell::new(5);
246        let weak1 = cell.downgrade();
247        let weak2 = weak1.clone();
248        assert_eq!(cell.weak_count(), 2);
249        assert_eq!(*weak2.upgrade().unwrap().borrow(), 5);
250    }
251
252    #[rstest]
253    fn test_try_borrow_fails_while_mutably_borrowed() {
254        let cell = SharedCell::new(0);
255        let _guard = cell.borrow_mut();
256        assert!(cell.try_borrow().is_err());
257    }
258
259    #[rstest]
260    fn test_try_borrow_mut_fails_while_borrowed() {
261        let cell = SharedCell::new(0);
262        let _guard = cell.borrow();
263        assert!(cell.try_borrow_mut().is_err());
264    }
265
266    #[rstest]
267    fn test_from_rc_refcell_roundtrip() {
268        let rc = Rc::new(RefCell::new(5));
269        let cell = SharedCell::from(rc);
270        assert_eq!(*cell.borrow(), 5);
271
272        let back: Rc<RefCell<i32>> = cell.into();
273        assert_eq!(*back.borrow(), 5);
274    }
275
276    #[rstest]
277    fn test_from_weak_refcell_roundtrip() {
278        let rc = Rc::new(RefCell::new(7));
279        let weak_cell = WeakCell::from(Rc::downgrade(&rc));
280        assert_eq!(*weak_cell.upgrade().unwrap().borrow(), 7);
281
282        let back: Weak<RefCell<i32>> = weak_cell.into();
283        assert_eq!(*back.upgrade().unwrap().borrow(), 7);
284    }
285}