reactive_graph/
traits.rs

1//! A series of traits to implement the behavior of reactive primitive, especially signals.
2//!
3//! ## Principles
4//! 1. **Composition**: Most of the traits are implemented as combinations of more primitive base traits,
5//!    and blanket implemented for all types that implement those traits.
6//! 2. **Fallibility**: Most traits includes a `try_` variant, which returns `None` if the method
7//!    fails (e.g., if signals are arena allocated and this can't be found, or if an `RwLock` is
8//!    poisoned).
9//!
10//! ## Metadata Traits
11//! - [`DefinedAt`] is used for debugging in the case of errors and should be implemented for all
12//!   signal types.
13//! - [`IsDisposed`] checks whether a signal is currently accessible.
14//!
15//! ## Base Traits
16//! | Trait             | Mode  | Description                                                                           |
17//! |-------------------|-------|---------------------------------------------------------------------------------------|
18//! | [`Track`]         | —     | Tracks changes to this value, adding it as a source of the current reactive observer. |
19//! | [`Notify`]       | —      | Notifies subscribers that this value has changed.                                     |
20//! | [`ReadUntracked`] | Guard | Gives immutable access to the value of this signal.                                   |
21//! | [`Write`]     | Guard | Gives mutable access to the value of this signal.
22//!
23//! ## Derived Traits
24//!
25//! ### Access
26//! | Trait             | Mode          | Composition                   | Description
27//! |-------------------|---------------|-------------------------------|------------
28//! | [`WithUntracked`] | `fn(&T) -> U` | [`ReadUntracked`]                  | Applies closure to the current value of the signal and returns result.
29//! | [`With`]          | `fn(&T) -> U` | [`ReadUntracked`] + [`Track`]      | Applies closure to the current value of the signal and returns result, with reactive tracking.
30//! | [`GetUntracked`]  | `T`           | [`WithUntracked`] + [`Clone`] | Clones the current value of the signal.
31//! | [`Get`]           | `T`           | [`GetUntracked`] + [`Track`]  | Clones the current value of the signal, with reactive tracking.
32//!
33//! ### Update
34//! | Trait               | Mode          | Composition                       | Description
35//! |---------------------|---------------|-----------------------------------|------------
36//! | [`UpdateUntracked`] | `fn(&mut T)`  | [`Write`]                     | Applies closure to the current value to update it, but doesn't notify subscribers.
37//! | [`Update`]          | `fn(&mut T)`  | [`UpdateUntracked`] + [`Notify`] | Applies closure to the current value to update it, and notifies subscribers.
38//! | [`Set`]             | `T`           | [`Update`]                        | Sets the value to a new value, and notifies subscribers.
39//!
40//! ## Using the Traits
41//!
42//! These traits are designed so that you can implement as few as possible, and the rest will be
43//! implemented automatically.
44//!
45//! For example, if you have a struct for which you can implement [`ReadUntracked`] and [`Track`], then
46//! [`WithUntracked`] and [`With`] will be implemented automatically (as will [`GetUntracked`] and
47//! [`Get`] for `Clone` types). But if you cannot implement [`ReadUntracked`] (because, for example,
48//! there isn't an `RwLock` so you can't wrap in a [`ReadGuard`](crate::signal::guards::ReadGuard),
49//! but you can still implement [`WithUntracked`] and [`Track`], the same traits will still be implemented.
50
51pub use crate::trait_options::*;
52use crate::{
53    effect::Effect,
54    graph::{Observer, Source, Subscriber, ToAnySource},
55    owner::Owner,
56    signal::{arc_signal, guards::UntrackedWriteGuard, ArcReadSignal},
57};
58use any_spawner::Executor;
59use futures::{Stream, StreamExt};
60use std::{
61    ops::{Deref, DerefMut},
62    panic::Location,
63};
64
65#[doc(hidden)]
66/// Provides a sensible panic message for accessing disposed signals.
67#[macro_export]
68macro_rules! unwrap_signal {
69    ($signal:ident) => {{
70        #[cfg(any(debug_assertions, leptos_debuginfo))]
71        let location = std::panic::Location::caller();
72        || {
73            #[cfg(any(debug_assertions, leptos_debuginfo))]
74            {
75                panic!(
76                    "{}",
77                    $crate::traits::panic_getting_disposed_signal(
78                        $signal.defined_at(),
79                        location
80                    )
81                );
82            }
83            #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
84            {
85                panic!(
86                    "Tried to access a reactive value that has already been \
87                     disposed."
88                );
89            }
90        }
91    }};
92}
93
94/// Allows disposing an arena-allocated signal before its owner has been disposed.
95pub trait Dispose {
96    /// Disposes of the signal. This:
97    /// 1. Detaches the signal from the reactive graph, preventing it from triggering
98    ///    further updates; and
99    /// 2. Drops the value contained in the signal.
100    fn dispose(self);
101}
102
103/// Allows tracking the value of some reactive data.
104pub trait Track {
105    /// Subscribes to this signal in the current reactive scope without doing anything with its value.
106    #[track_caller]
107    fn track(&self);
108}
109
110impl<T: Source + ToAnySource + DefinedAt> Track for T {
111    #[track_caller]
112    fn track(&self) {
113        if self.is_disposed() {
114            return;
115        }
116
117        if let Some(subscriber) = Observer::get() {
118            subscriber.add_source(self.to_any_source());
119            self.add_subscriber(subscriber);
120        } else {
121            #[cfg(all(debug_assertions, feature = "effects"))]
122            {
123                use crate::diagnostics::SpecialNonReactiveZone;
124
125                if !SpecialNonReactiveZone::is_inside() {
126                    let called_at = Location::caller();
127                    let ty = std::any::type_name::<T>();
128                    let defined_at = self
129                        .defined_at()
130                        .map(ToString::to_string)
131                        .unwrap_or_else(|| String::from("{unknown}"));
132                    crate::log_warning(format_args!(
133                        "At {called_at}, you access a {ty} (defined at \
134                         {defined_at}) outside a reactive tracking context. \
135                         This might mean your app is not responding to \
136                         changes in signal values in the way you \
137                         expect.\n\nHere’s how to fix it:\n\n1. If this is \
138                         inside a `view!` macro, make sure you are passing a \
139                         function, not a value.\n  ❌ NO  <p>{{x.get() * \
140                         2}}</p>\n  ✅ YES <p>{{move || x.get() * \
141                         2}}</p>\n\n2. If it’s in the body of a component, \
142                         try wrapping this access in a closure: \n  ❌ NO  \
143                         let y = x.get() * 2\n  ✅ YES let y = move || \
144                         x.get() * 2.\n\n3. If you’re *trying* to access the \
145                         value without tracking, use `.get_untracked()` or \
146                         `.with_untracked()` instead."
147                    ));
148                }
149            }
150        }
151    }
152}
153
154/// Give read-only access to a signal's value by reference through a guard type,
155/// without tracking the value reactively.
156pub trait ReadUntracked: Sized + DefinedAt {
157    /// The guard type that will be returned, which can be dereferenced to the value.
158    type Value: Deref;
159
160    /// Returns the guard, or `None` if the signal has already been disposed.
161    #[track_caller]
162    fn try_read_untracked(&self) -> Option<Self::Value>;
163
164    /// Returns the guard.
165    ///
166    /// # Panics
167    /// Panics if you try to access a signal that has been disposed.
168    #[track_caller]
169    fn read_untracked(&self) -> Self::Value {
170        self.try_read_untracked()
171            .unwrap_or_else(unwrap_signal!(self))
172    }
173
174    /// This is a backdoor to allow overriding the [`Read::try_read`] implementation despite it being auto implemented.
175    ///
176    /// If your type contains a [`Signal`](crate::wrappers::read::Signal),
177    /// call it's [`ReadUntracked::custom_try_read`] here, else return `None`.
178    #[track_caller]
179    fn custom_try_read(&self) -> Option<Option<Self::Value>> {
180        None
181    }
182}
183
184/// Give read-only access to a signal's value by reference through a guard type,
185/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
186pub trait Read: DefinedAt {
187    /// The guard type that will be returned, which can be dereferenced to the value.
188    type Value: Deref;
189
190    /// Subscribes to the signal, and returns the guard, or `None` if the signal has already been disposed.
191    #[track_caller]
192    fn try_read(&self) -> Option<Self::Value>;
193
194    /// Subscribes to the signal, and returns the guard.
195    ///
196    /// # Panics
197    /// Panics if you try to access a signal that has been disposed.
198    #[track_caller]
199    fn read(&self) -> Self::Value {
200        self.try_read().unwrap_or_else(unwrap_signal!(self))
201    }
202}
203
204impl<T> Read for T
205where
206    T: Track + ReadUntracked,
207{
208    type Value = T::Value;
209
210    fn try_read(&self) -> Option<Self::Value> {
211        // The [`Read`] trait is auto implemented for types that implement [`ReadUntracked`] + [`Track`]. The [`Read`] trait then auto implements the [`With`] and [`Get`] traits too.
212        //
213        // This is a problem for e.g. the [`Signal`](crate::wrappers::read::Signal) type,
214        // this type must use a custom [`Read::try_read`] implementation to avoid an unnecessary clone.
215        //
216        // This is a backdoor to allow overriding the [`Read::try_read`] implementation despite it being auto implemented.
217        if let Some(custom) = self.custom_try_read() {
218            custom
219        } else {
220            self.track();
221            self.try_read_untracked()
222        }
223    }
224}
225
226/// A reactive, mutable guard that can be untracked to prevent it from notifying subscribers when
227/// it is dropped.
228pub trait UntrackableGuard: DerefMut {
229    /// Removes the notifier from the guard, such that it will no longer notify subscribers when it is dropped.
230    fn untrack(&mut self);
231}
232
233impl<T> UntrackableGuard for Box<dyn UntrackableGuard<Target = T>> {
234    fn untrack(&mut self) {
235        (**self).untrack();
236    }
237}
238
239/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
240/// signal's subscribers will be notified.
241pub trait Write: Sized + DefinedAt + Notify {
242    /// The type of the signal's value.
243    type Value: Sized + 'static;
244
245    /// Returns the guard, or `None` if the signal has already been disposed.
246    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>>;
247
248    // Returns a guard that will not notify subscribers when dropped,
249    /// or `None` if the signal has already been disposed.
250    fn try_write_untracked(
251        &self,
252    ) -> Option<impl DerefMut<Target = Self::Value>>;
253
254    /// Returns the guard.
255    ///
256    /// # Panics
257    /// Panics if you try to access a signal that has been disposed.
258    fn write(&self) -> impl UntrackableGuard<Target = Self::Value> {
259        self.try_write().unwrap_or_else(unwrap_signal!(self))
260    }
261
262    /// Returns a guard that will not notify subscribers when dropped.
263    ///
264    /// # Panics
265    /// Panics if you try to access a signal that has been disposed.
266    fn write_untracked(&self) -> impl DerefMut<Target = Self::Value> {
267        self.try_write_untracked()
268            .unwrap_or_else(unwrap_signal!(self))
269    }
270}
271
272/// Give read-only access to a signal's value by reference inside a closure,
273/// without tracking the value reactively.
274pub trait WithUntracked: DefinedAt {
275    /// The type of the value contained in the signal.
276    type Value: ?Sized;
277
278    /// Applies the closure to the value, and returns the result,
279    /// or `None` if the signal has already been disposed.
280    #[track_caller]
281    fn try_with_untracked<U>(
282        &self,
283        fun: impl FnOnce(&Self::Value) -> U,
284    ) -> Option<U>;
285
286    /// Applies the closure to the value, and returns the result.
287    ///
288    /// # Panics
289    /// Panics if you try to access a signal that has been disposed.
290    #[track_caller]
291    fn with_untracked<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
292        self.try_with_untracked(fun)
293            .unwrap_or_else(unwrap_signal!(self))
294    }
295}
296
297impl<T> WithUntracked for T
298where
299    T: DefinedAt + ReadUntracked,
300{
301    type Value = <<Self as ReadUntracked>::Value as Deref>::Target;
302
303    fn try_with_untracked<U>(
304        &self,
305        fun: impl FnOnce(&Self::Value) -> U,
306    ) -> Option<U> {
307        self.try_read_untracked().map(|value| fun(&value))
308    }
309}
310
311/// Give read-only access to a signal's value by reference inside a closure,
312/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
313pub trait With: DefinedAt {
314    /// The type of the value contained in the signal.
315    type Value: ?Sized;
316
317    /// Subscribes to the signal, applies the closure to the value, and returns the result,
318    /// or `None` if the signal has already been disposed.
319    #[track_caller]
320    fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U>;
321
322    /// Subscribes to the signal, applies the closure to the value, and returns the result.
323    ///
324    /// # Panics
325    /// Panics if you try to access a signal that has been disposed.
326    #[track_caller]
327    fn with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
328        self.try_with(fun).unwrap_or_else(unwrap_signal!(self))
329    }
330}
331
332impl<T> With for T
333where
334    T: Read,
335{
336    type Value = <<T as Read>::Value as Deref>::Target;
337
338    #[track_caller]
339    fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U> {
340        self.try_read().map(|val| fun(&val))
341    }
342}
343
344/// Clones the value of the signal, without tracking the value reactively.
345pub trait GetUntracked: DefinedAt {
346    /// The type of the value contained in the signal.
347    type Value;
348
349    /// Clones and returns the value of the signal,
350    /// or `None` if the signal has already been disposed.
351    #[track_caller]
352    fn try_get_untracked(&self) -> Option<Self::Value>;
353
354    /// Clones and returns the value of the signal,
355    ///
356    /// # Panics
357    /// Panics if you try to access a signal that has been disposed.
358    #[track_caller]
359    fn get_untracked(&self) -> Self::Value {
360        self.try_get_untracked()
361            .unwrap_or_else(unwrap_signal!(self))
362    }
363}
364
365impl<T> GetUntracked for T
366where
367    T: WithUntracked,
368    T::Value: Clone,
369{
370    type Value = <Self as WithUntracked>::Value;
371
372    fn try_get_untracked(&self) -> Option<Self::Value> {
373        self.try_with_untracked(Self::Value::clone)
374    }
375}
376
377/// Clones the value of the signal, without tracking the value reactively.
378/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
379pub trait Get: DefinedAt {
380    /// The type of the value contained in the signal.
381    type Value: Clone;
382
383    /// Subscribes to the signal, then clones and returns the value of the signal,
384    /// or `None` if the signal has already been disposed.
385    #[track_caller]
386    fn try_get(&self) -> Option<Self::Value>;
387
388    /// Subscribes to the signal, then clones and returns the value of the signal.
389    ///
390    /// # Panics
391    /// Panics if you try to access a signal that has been disposed.
392    #[track_caller]
393    fn get(&self) -> Self::Value {
394        self.try_get().unwrap_or_else(unwrap_signal!(self))
395    }
396}
397
398impl<T> Get for T
399where
400    T: With,
401    T::Value: Clone,
402{
403    type Value = <T as With>::Value;
404
405    #[track_caller]
406    fn try_get(&self) -> Option<Self::Value> {
407        self.try_with(Self::Value::clone)
408    }
409}
410
411/// Notifies subscribers of a change in this signal.
412pub trait Notify {
413    /// Notifies subscribers of a change in this signal.
414    #[track_caller]
415    fn notify(&self);
416}
417
418/// Updates the value of a signal by applying a function that updates it in place,
419/// without notifying subscribers.
420pub trait UpdateUntracked: DefinedAt {
421    /// The type of the value contained in the signal.
422    type Value;
423
424    /// Updates the value by applying a function, returning the value returned by that function.
425    /// Does not notify subscribers that the signal has changed.
426    ///
427    /// # Panics
428    /// Panics if you try to update a signal that has been disposed.
429    #[track_caller]
430    fn update_untracked<U>(
431        &self,
432        fun: impl FnOnce(&mut Self::Value) -> U,
433    ) -> U {
434        self.try_update_untracked(fun)
435            .unwrap_or_else(unwrap_signal!(self))
436    }
437
438    /// Updates the value by applying a function, returning the value returned by that function,
439    /// or `None` if the signal has already been disposed.
440    /// Does not notify subscribers that the signal has changed.
441    fn try_update_untracked<U>(
442        &self,
443        fun: impl FnOnce(&mut Self::Value) -> U,
444    ) -> Option<U>;
445}
446
447impl<T> UpdateUntracked for T
448where
449    T: Write,
450{
451    type Value = <Self as Write>::Value;
452
453    #[track_caller]
454    fn try_update_untracked<U>(
455        &self,
456        fun: impl FnOnce(&mut Self::Value) -> U,
457    ) -> Option<U> {
458        let mut guard = self.try_write_untracked()?;
459        Some(fun(&mut *guard))
460    }
461}
462
463/// Updates the value of a signal by applying a function that updates it in place,
464/// notifying its subscribers that the value has changed.
465pub trait Update {
466    /// The type of the value contained in the signal.
467    type Value;
468
469    /// Updates the value of the signal and notifies subscribers.
470    #[track_caller]
471    fn update(&self, fun: impl FnOnce(&mut Self::Value)) {
472        self.try_update(fun);
473    }
474
475    /// Updates the value of the signal, but only notifies subscribers if the function
476    /// returns `true`.
477    #[track_caller]
478    fn maybe_update(&self, fun: impl FnOnce(&mut Self::Value) -> bool) {
479        self.try_maybe_update(|val| {
480            let did_update = fun(val);
481            (did_update, ())
482        });
483    }
484
485    /// Updates the value of the signal and notifies subscribers, returning the value that is
486    /// returned by the update function, or `None` if the signal has already been disposed.
487    #[track_caller]
488    fn try_update<U>(
489        &self,
490        fun: impl FnOnce(&mut Self::Value) -> U,
491    ) -> Option<U> {
492        self.try_maybe_update(|val| (true, fun(val)))
493    }
494
495    /// Updates the value of the signal, notifying subscribers if the update function returns
496    /// `(true, _)`, and returns the value returned by the update function,
497    /// or `None` if the signal has already been disposed.
498    fn try_maybe_update<U>(
499        &self,
500        fun: impl FnOnce(&mut Self::Value) -> (bool, U),
501    ) -> Option<U>;
502}
503
504impl<T> Update for T
505where
506    T: Write,
507{
508    type Value = <Self as Write>::Value;
509
510    #[track_caller]
511    fn try_maybe_update<U>(
512        &self,
513        fun: impl FnOnce(&mut Self::Value) -> (bool, U),
514    ) -> Option<U> {
515        let mut lock = self.try_write()?;
516        let (did_update, val) = fun(&mut *lock);
517        if !did_update {
518            lock.untrack();
519        }
520        drop(lock);
521        Some(val)
522    }
523}
524
525/// Updates the value of the signal by replacing it.
526pub trait Set {
527    /// The type of the value contained in the signal.
528    type Value;
529
530    /// Updates the value by replacing it, and notifies subscribers that it has changed.
531    fn set(&self, value: Self::Value);
532
533    /// Updates the value by replacing it, and notifies subscribers that it has changed.
534    ///
535    /// If the signal has already been disposed, returns `Some(value)` with the value that was
536    /// passed in. Otherwise, returns `None`.
537    fn try_set(&self, value: Self::Value) -> Option<Self::Value>;
538}
539
540impl<T> Set for T
541where
542    T: Update + IsDisposed,
543{
544    type Value = <Self as Update>::Value;
545
546    #[track_caller]
547    fn set(&self, value: Self::Value) {
548        self.try_update(|n| *n = value);
549    }
550
551    #[track_caller]
552    fn try_set(&self, value: Self::Value) -> Option<Self::Value> {
553        if self.is_disposed() {
554            Some(value)
555        } else {
556            self.set(value);
557            None
558        }
559    }
560}
561
562/// Allows converting a signal into an async [`Stream`].
563pub trait ToStream<T> {
564    /// Generates a [`Stream`] that emits the new value of the signal
565    /// whenever it changes.
566    ///
567    /// # Panics
568    /// Panics if you try to access a signal that is owned by a reactive node that has been disposed.
569    #[track_caller]
570    fn to_stream(&self) -> impl Stream<Item = T> + Send;
571}
572
573impl<S> ToStream<S::Value> for S
574where
575    S: Clone + Get + Send + Sync + 'static,
576    S::Value: Send + 'static,
577{
578    fn to_stream(&self) -> impl Stream<Item = S::Value> + Send {
579        let (tx, rx) = futures::channel::mpsc::unbounded();
580
581        let close_channel = tx.clone();
582
583        Owner::on_cleanup(move || close_channel.close_channel());
584
585        Effect::new_isomorphic({
586            let this = self.clone();
587            move |_| {
588                let _ = tx.unbounded_send(this.get());
589            }
590        });
591
592        rx
593    }
594}
595
596/// Allows creating a signal from an async [`Stream`].
597pub trait FromStream<T> {
598    /// Creates a signal that contains the latest value of the stream.
599    #[track_caller]
600    fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self;
601
602    /// Creates a signal that contains the latest value of the stream.
603    #[track_caller]
604    fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self;
605}
606
607impl<S, T> FromStream<T> for S
608where
609    S: From<ArcReadSignal<Option<T>>> + Send + Sync,
610    T: Send + Sync + 'static,
611{
612    fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self {
613        let (read, write) = arc_signal(None);
614        let mut stream = Box::pin(stream);
615        crate::spawn(async move {
616            while let Some(value) = stream.next().await {
617                write.set(Some(value));
618            }
619        });
620        read.into()
621    }
622
623    fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self {
624        let (read, write) = arc_signal(None);
625        let mut stream = Box::pin(stream);
626        Executor::spawn_local(async move {
627            while let Some(value) = stream.next().await {
628                write.set(Some(value));
629            }
630        });
631        read.into()
632    }
633}
634
635/// Checks whether a signal has already been disposed.
636pub trait IsDisposed {
637    /// If `true`, the signal cannot be accessed without a panic.
638    fn is_disposed(&self) -> bool;
639}
640
641/// Turns a signal back into a raw value.
642pub trait IntoInner {
643    /// The type of the value contained in the signal.
644    type Value;
645
646    /// Returns the inner value if this is the only reference to to the signal.
647    /// Otherwise, returns `None` and drops this reference.
648    /// # Panics
649    /// Panics if the inner lock is poisoned.
650    fn into_inner(self) -> Option<Self::Value>;
651}
652
653/// Describes where the signal was defined. This is used for diagnostic warnings and is purely a
654/// debug-mode tool.
655pub trait DefinedAt {
656    /// Returns the location at which the signal was defined. This is usually simply `None` in
657    /// release mode.
658    fn defined_at(&self) -> Option<&'static Location<'static>>;
659}
660
661#[doc(hidden)]
662pub fn panic_getting_disposed_signal(
663    defined_at: Option<&'static Location<'static>>,
664    location: &'static Location<'static>,
665) -> String {
666    if let Some(defined_at) = defined_at {
667        format!(
668            "At {location}, you tried to access a reactive value which was \
669             defined at {defined_at}, but it has already been disposed."
670        )
671    } else {
672        format!(
673            "At {location}, you tried to access a reactive value, but it has \
674             already been disposed."
675        )
676    }
677}
678
679/// A variation of the [`Read`] trait that provides a signposted "always-non-reactive" API.
680/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
681pub trait ReadValue: Sized + DefinedAt {
682    /// The guard type that will be returned, which can be dereferenced to the value.
683    type Value: Deref;
684
685    /// Returns the non-reactive guard, or `None` if the value has already been disposed.
686    #[track_caller]
687    fn try_read_value(&self) -> Option<Self::Value>;
688
689    /// Returns the non-reactive guard.
690    ///
691    /// # Panics
692    /// Panics if you try to access a value that has been disposed.
693    #[track_caller]
694    fn read_value(&self) -> Self::Value {
695        self.try_read_value().unwrap_or_else(unwrap_signal!(self))
696    }
697}
698
699/// A variation of the [`With`] trait that provides a signposted "always-non-reactive" API.
700/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
701pub trait WithValue: DefinedAt {
702    /// The type of the value contained in the value.
703    type Value: ?Sized;
704
705    /// Applies the closure to the value, non-reactively, and returns the result,
706    /// or `None` if the value has already been disposed.
707    #[track_caller]
708    fn try_with_value<U>(
709        &self,
710        fun: impl FnOnce(&Self::Value) -> U,
711    ) -> Option<U>;
712
713    /// Applies the closure to the value, non-reactively, and returns the result.
714    ///
715    /// # Panics
716    /// Panics if you try to access a value that has been disposed.
717    #[track_caller]
718    fn with_value<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
719        self.try_with_value(fun)
720            .unwrap_or_else(unwrap_signal!(self))
721    }
722}
723
724impl<T> WithValue for T
725where
726    T: DefinedAt + ReadValue,
727{
728    type Value = <<Self as ReadValue>::Value as Deref>::Target;
729
730    fn try_with_value<U>(
731        &self,
732        fun: impl FnOnce(&Self::Value) -> U,
733    ) -> Option<U> {
734        self.try_read_value().map(|value| fun(&value))
735    }
736}
737
738/// A variation of the [`Get`] trait that provides a signposted "always-non-reactive" API.
739/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
740pub trait GetValue: DefinedAt {
741    /// The type of the value contained in the value.
742    type Value: Clone;
743
744    /// Clones and returns the value of the value, non-reactively,
745    /// or `None` if the value has already been disposed.
746    #[track_caller]
747    fn try_get_value(&self) -> Option<Self::Value>;
748
749    /// Clones and returns the value of the value, non-reactively.
750    ///
751    /// # Panics
752    /// Panics if you try to access a value that has been disposed.
753    #[track_caller]
754    fn get_value(&self) -> Self::Value {
755        self.try_get_value().unwrap_or_else(unwrap_signal!(self))
756    }
757}
758
759impl<T> GetValue for T
760where
761    T: WithValue,
762    T::Value: Clone,
763{
764    type Value = <Self as WithValue>::Value;
765
766    fn try_get_value(&self) -> Option<Self::Value> {
767        self.try_with_value(Self::Value::clone)
768    }
769}
770
771/// A variation of the [`Write`] trait that provides a signposted "always-non-reactive" API.
772/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
773pub trait WriteValue: Sized + DefinedAt {
774    /// The type of the value's value.
775    type Value: Sized + 'static;
776
777    /// Returns a non-reactive write guard, or `None` if the value has already been disposed.
778    #[track_caller]
779    fn try_write_value(&self) -> Option<UntrackedWriteGuard<Self::Value>>;
780
781    /// Returns a non-reactive write guard.
782    ///
783    /// # Panics
784    /// Panics if you try to access a value that has been disposed.
785    #[track_caller]
786    fn write_value(&self) -> UntrackedWriteGuard<Self::Value> {
787        self.try_write_value().unwrap_or_else(unwrap_signal!(self))
788    }
789}
790
791/// A variation of the [`Update`] trait that provides a signposted "always-non-reactive" API.
792/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
793pub trait UpdateValue: DefinedAt {
794    /// The type of the value contained in the value.
795    type Value;
796
797    /// Updates the value, returning the value that is
798    /// returned by the update function, or `None` if the value has already been disposed.
799    #[track_caller]
800    fn try_update_value<U>(
801        &self,
802        fun: impl FnOnce(&mut Self::Value) -> U,
803    ) -> Option<U>;
804
805    /// Updates the value.
806    #[track_caller]
807    fn update_value(&self, fun: impl FnOnce(&mut Self::Value)) {
808        self.try_update_value(fun);
809    }
810}
811
812impl<T> UpdateValue for T
813where
814    T: WriteValue,
815{
816    type Value = <Self as WriteValue>::Value;
817
818    #[track_caller]
819    fn try_update_value<U>(
820        &self,
821        fun: impl FnOnce(&mut Self::Value) -> U,
822    ) -> Option<U> {
823        let mut guard = self.try_write_value()?;
824        Some(fun(&mut *guard))
825    }
826}
827
828/// A variation of the [`Set`] trait that provides a signposted "always-non-reactive" API.
829/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
830pub trait SetValue: DefinedAt {
831    /// The type of the value contained in the value.
832    type Value;
833
834    /// Updates the value by replacing it, non-reactively.
835    ///
836    /// If the value has already been disposed, returns `Some(value)` with the value that was
837    /// passed in. Otherwise, returns `None`.
838    #[track_caller]
839    fn try_set_value(&self, value: Self::Value) -> Option<Self::Value>;
840
841    /// Updates the value by replacing it, non-reactively.
842    #[track_caller]
843    fn set_value(&self, value: Self::Value) {
844        self.try_set_value(value);
845    }
846}
847
848impl<T> SetValue for T
849where
850    T: WriteValue,
851{
852    type Value = <Self as WriteValue>::Value;
853
854    fn try_set_value(&self, value: Self::Value) -> Option<Self::Value> {
855        // Unlike most other traits, for these None actually means success:
856        if let Some(mut guard) = self.try_write_value() {
857            *guard = value;
858            None
859        } else {
860            Some(value)
861        }
862    }
863}