Skip to main content

rx_rs/core/
rx_ref.rs

1use super::rx_observable::RxObservable;
2use super::rx_subject::RxSubject;
3use super::rx_val::RxVal;
4
5/// A read-write reactive value that holds a current state.
6///
7/// RxRef provides both read and write access to a reactive value. It exposes
8/// a read-only RxVal via the `.val()` method, and allows updating the value
9/// via the `.set()` method.
10///
11/// When the value is set, all subscribers to the RxVal are notified.
12///
13/// # Example
14/// ```
15/// use rx_rs::core::{RxRef, DisposableTracker};
16///
17/// let mut tracker = DisposableTracker::new();
18/// let client_id = RxRef::new(None);
19///
20/// // Subscribe to changes
21/// client_id.val().subscribe(tracker.tracker(), |maybe_id| {
22///     match maybe_id {
23///         Some(id) => println!("Client ID assigned: {}", id),
24///         None => println!("No client ID yet"),
25///     }
26/// }); // Prints "No client ID yet" immediately
27///
28/// // Update the value
29/// client_id.set(Some(42)); // Prints "Client ID assigned: 42"
30/// ```
31#[derive(Clone)]
32pub struct RxRef<T> {
33    inner: RxVal<T>,
34}
35
36impl<T: 'static> RxRef<T>
37where
38    T: Clone + PartialEq,
39{
40    /// Creates a new RxRef with the given initial value.
41    ///
42    /// # Arguments
43    /// * `value` - The initial value
44    ///
45    /// # Example
46    /// ```
47    /// use rx_rs::core::RxRef;
48    ///
49    /// let connection_state = RxRef::new("Disconnected");
50    /// ```
51    pub fn new(value: T) -> Self {
52        Self {
53            inner: RxVal::new(value),
54        }
55    }
56
57    /// Sets a new value and notifies all subscribers.
58    ///
59    /// All subscribers to the RxVal obtained via `.val()` will be called
60    /// with the new value.
61    ///
62    /// # Arguments
63    /// * `value` - The new value to set
64    ///
65    /// # Example
66    /// ```
67    /// use rx_rs::core::RxRef;
68    ///
69    /// let state = RxRef::new(0);
70    /// state.set(42);
71    /// assert_eq!(state.get(), 42);
72    /// ```
73    pub fn set(&self, value: T) {
74        self.inner.update(value);
75    }
76
77    /// Gets the current value.
78    ///
79    /// This is a convenience method that delegates to the underlying RxVal.
80    ///
81    /// # Example
82    /// ```
83    /// use rx_rs::core::RxRef;
84    ///
85    /// let counter = RxRef::new(5);
86    /// assert_eq!(counter.get(), 5);
87    /// ```
88    pub fn get(&self) -> T {
89        self.inner.get()
90    }
91
92    /// Returns a read-only view of this reactive value.
93    ///
94    /// The returned RxVal can be cloned and passed around, allowing multiple
95    /// parts of the code to subscribe to changes without having write access.
96    ///
97    /// # Example
98    /// ```
99    /// use rx_rs::core::{RxRef, DisposableTracker};
100    ///
101    /// let mut tracker = DisposableTracker::new();
102    /// let state = RxRef::new("initial");
103    /// let read_only = state.val();
104    ///
105    /// read_only.subscribe(tracker.tracker(), |val| {
106    ///     println!("State: {}", val);
107    /// });
108    /// ```
109    pub fn val(&self) -> RxVal<T> {
110        self.inner.clone()
111    }
112
113    /// Modifies the value using a closure and notifies subscribers.
114    ///
115    /// This is useful when you need to update the value based on its current
116    /// state, without having to clone it first.
117    ///
118    /// # Arguments
119    /// * `f` - Function that receives a mutable reference to the current value
120    ///
121    /// # Example
122    /// ```
123    /// use rx_rs::core::RxRef;
124    ///
125    /// let counter = RxRef::new(0);
126    /// counter.modify(|count| *count += 1);
127    /// assert_eq!(counter.get(), 1);
128    /// ```
129    pub fn modify<F>(&self, f: F)
130    where
131        F: FnOnce(&mut T),
132    {
133        let mut current = self.get();
134        f(&mut current);
135        self.set(current);
136    }
137
138    /// Returns the number of active subscribers.
139    pub fn subscriber_count(&self) -> usize {
140        self.inner.subscriber_count()
141    }
142
143    /// Returns a pointer address for debugging identity.
144    /// Use this to check if two RxRef instances share the same underlying data.
145    pub fn debug_ptr(&self) -> usize {
146        self.inner.debug_ptr()
147    }
148
149    /// Converts this RxRef into a stream (RxObservable).
150    ///
151    /// The returned observable emits the current value immediately on subscription,
152    /// and then emits all future changes.
153    ///
154    /// This is a convenience method that delegates to the underlying RxVal's `.stream()`.
155    ///
156    /// # Example
157    /// ```
158    /// use rx_rs::core::{RxRef, DisposableTracker};
159    ///
160    /// let mut tracker = DisposableTracker::new();
161    /// let state = RxRef::new(0);
162    /// let stream = state.stream();
163    ///
164    /// stream.subscribe(tracker.tracker(), |value| {
165    ///     println!("State: {}", value);
166    /// }); // Prints "State: 0" immediately
167    ///
168    /// state.set(42); // Prints "State: 42"
169    /// ```
170    pub fn stream(&self) -> RxObservable<T> {
171        self.inner.stream()
172    }
173
174    /// Maps the values of this RxRef using a transformation function.
175    ///
176    /// Returns a new RxVal that always contains the transformed value.
177    /// When the source RxRef changes, the transformation is applied and
178    /// the resulting RxVal is updated.
179    ///
180    /// # Arguments
181    /// * `f` - Function to transform values from A to B
182    ///
183    /// # Example
184    /// ```
185    /// use rx_rs::core::RxRef;
186    ///
187    /// let number = RxRef::new(5);
188    /// let doubled = number.map(|x| x * 2);
189    ///
190    /// assert_eq!(doubled.get(), 10);
191    ///
192    /// number.set(10);
193    /// assert_eq!(doubled.get(), 20);
194    /// ```
195    pub fn map<B, F>(&self, f: F) -> RxVal<B>
196    where
197        B: Clone + PartialEq + 'static,
198        F: Fn(&T) -> B + 'static,
199    {
200        self.inner.map(f)
201    }
202
203    /// Flat-maps the values of this RxRef using a function that returns RxVal<B>.
204    ///
205    /// When the source RxRef changes, the function is called to produce a new RxVal<B>,
206    /// and the result RxVal is updated to reflect the current value of that inner RxVal.
207    /// The result also updates when the inner RxVal changes.
208    ///
209    /// This is a convenience method that delegates to the underlying RxVal's `.flat_map()`.
210    ///
211    /// # Arguments
212    /// * `f` - Function to transform values from A to RxVal<B>
213    ///
214    /// # Example
215    /// ```
216    /// use rx_rs::core::RxRef;
217    ///
218    /// let outer = RxRef::new(1);
219    /// let inner1 = RxRef::new(10);
220    /// let inner2 = RxRef::new(20);
221    ///
222    /// let inner1_clone = inner1.clone();
223    /// let inner2_clone = inner2.clone();
224    ///
225    /// let flattened = outer.flat_map(move |&x| {
226    ///     if x == 1 { inner1_clone.val() } else { inner2_clone.val() }
227    /// });
228    ///
229    /// assert_eq!(flattened.get(), 10);
230    ///
231    /// outer.set(2);
232    /// assert_eq!(flattened.get(), 20);
233    /// ```
234    pub fn flat_map<B, F>(&self, f: F) -> RxVal<B>
235    where
236        B: Clone + PartialEq + 'static,
237        F: Fn(&T) -> RxVal<B> + 'static,
238    {
239        self.inner.flat_map(f)
240    }
241
242    /// Flat-maps using a function that returns RxRef<B>.
243    pub fn flat_map_ref<B, F>(&self, f: F) -> RxVal<B>
244    where
245        B: Clone + PartialEq + 'static,
246        F: Fn(&T) -> RxRef<B> + 'static,
247    {
248        self.inner.flat_map_ref(f)
249    }
250
251    /// Flat-maps using a function that returns RxObservable<B>.
252    pub fn flat_map_observable<B, F>(&self, f: F) -> RxObservable<B>
253    where
254        B: Clone + 'static,
255        F: Fn(&T) -> RxObservable<B> + 'static,
256    {
257        self.inner.flat_map_observable(f)
258    }
259
260    /// Flat-maps using a function that returns RxSubject<B>.
261    pub fn flat_map_subject<B, F>(&self, f: F) -> RxObservable<B>
262    where
263        B: Clone + 'static,
264        F: Fn(&T) -> RxSubject<B> + 'static,
265    {
266        self.inner.flat_map_subject(f)
267    }
268
269    /// Combines this RxRef with another RxVal.
270    pub fn zip_val<U>(&self, other: RxVal<U>) -> RxVal<(T, U)>
271    where
272        U: Clone + PartialEq + 'static,
273    {
274        self.inner.zip_val(other)
275    }
276
277    /// Combines this RxRef with another RxRef.
278    pub fn zip_ref<U>(&self, other: RxRef<U>) -> RxVal<(T, U)>
279    where
280        U: Clone + PartialEq + 'static,
281    {
282        self.inner.zip_ref(other)
283    }
284}