Skip to main content

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        let failed = self.try_update(|n| *n = value).is_none();
549
550        #[cfg(any(debug_assertions, leptos_debuginfo))]
551        if failed && !self.is_disposed() {
552            let called_at = Location::caller();
553            let ty = std::any::type_name::<Self::Value>();
554
555            crate::log_warning(format_args!(
556                "At {called_at}, you tried to update a {ty}, but the update \
557                 failed. This can happen if a read guard over the value is \
558                 still alive."
559            ));
560        };
561    }
562
563    #[track_caller]
564    fn try_set(&self, value: Self::Value) -> Option<Self::Value> {
565        if self.is_disposed() {
566            Some(value)
567        } else {
568            self.set(value);
569            None
570        }
571    }
572}
573
574/// Allows converting a signal into an async [`Stream`].
575pub trait ToStream<T> {
576    /// Generates a [`Stream`] that emits the new value of the signal
577    /// whenever it changes.
578    ///
579    /// # Panics
580    /// Panics if you try to access a signal that is owned by a reactive node that has been disposed.
581    #[track_caller]
582    fn to_stream(&self) -> impl Stream<Item = T> + Send;
583}
584
585impl<S> ToStream<S::Value> for S
586where
587    S: Clone + Get + Send + Sync + 'static,
588    S::Value: Send + 'static,
589{
590    fn to_stream(&self) -> impl Stream<Item = S::Value> + Send {
591        let (tx, rx) = futures::channel::mpsc::unbounded();
592
593        let close_channel = tx.clone();
594
595        Owner::on_cleanup(move || close_channel.close_channel());
596
597        Effect::new_isomorphic({
598            let this = self.clone();
599            move |_| {
600                let _ = tx.unbounded_send(this.get());
601            }
602        });
603
604        rx
605    }
606}
607
608/// Allows creating a signal from an async [`Stream`].
609pub trait FromStream<T> {
610    /// Creates a signal that contains the latest value of the stream.
611    #[track_caller]
612    fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self;
613
614    /// Creates a signal that contains the latest value of the stream.
615    #[track_caller]
616    fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self;
617}
618
619impl<S, T> FromStream<T> for S
620where
621    S: From<ArcReadSignal<Option<T>>> + Send + Sync,
622    T: Send + Sync + 'static,
623{
624    fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self {
625        let (read, write) = arc_signal(None);
626        let mut stream = Box::pin(stream);
627        crate::spawn(async move {
628            while let Some(value) = stream.next().await {
629                write.set(Some(value));
630            }
631        });
632        read.into()
633    }
634
635    fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self {
636        let (read, write) = arc_signal(None);
637        let mut stream = Box::pin(stream);
638        Executor::spawn_local(async move {
639            while let Some(value) = stream.next().await {
640                write.set(Some(value));
641            }
642        });
643        read.into()
644    }
645}
646
647/// Checks whether a signal has already been disposed.
648pub trait IsDisposed {
649    /// If `true`, the signal cannot be accessed without a panic.
650    fn is_disposed(&self) -> bool;
651}
652
653/// Turns a signal back into a raw value.
654pub trait IntoInner {
655    /// The type of the value contained in the signal.
656    type Value;
657
658    /// Returns the inner value if this is the only reference to the signal.
659    /// Otherwise, returns `None` and drops this reference.
660    /// # Panics
661    /// Panics if the inner lock is poisoned.
662    fn into_inner(self) -> Option<Self::Value>;
663}
664
665/// Describes where the signal was defined. This is used for diagnostic warnings and is purely a
666/// debug-mode tool.
667pub trait DefinedAt {
668    /// Returns the location at which the signal was defined. This is usually simply `None` in
669    /// release mode.
670    fn defined_at(&self) -> Option<&'static Location<'static>>;
671}
672
673#[doc(hidden)]
674pub fn panic_getting_disposed_signal(
675    defined_at: Option<&'static Location<'static>>,
676    location: &'static Location<'static>,
677) -> String {
678    if let Some(defined_at) = defined_at {
679        format!(
680            "At {location}, you tried to access a reactive value which was \
681             defined at {defined_at}, but it has already been disposed."
682        )
683    } else {
684        format!(
685            "At {location}, you tried to access a reactive value, but it has \
686             already been disposed."
687        )
688    }
689}
690
691/// A variation of the [`Read`] trait that provides a signposted "always-non-reactive" API.
692/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
693pub trait ReadValue: Sized + DefinedAt {
694    /// The guard type that will be returned, which can be dereferenced to the value.
695    type Value: Deref;
696
697    /// Returns the non-reactive guard, or `None` if the value has already been disposed.
698    #[track_caller]
699    fn try_read_value(&self) -> Option<Self::Value>;
700
701    /// Returns the non-reactive guard.
702    ///
703    /// # Panics
704    /// Panics if you try to access a value that has been disposed.
705    #[track_caller]
706    fn read_value(&self) -> Self::Value {
707        self.try_read_value().unwrap_or_else(unwrap_signal!(self))
708    }
709}
710
711/// A variation of the [`With`] trait that provides a signposted "always-non-reactive" API.
712/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
713pub trait WithValue: DefinedAt {
714    /// The type of the value contained in the value.
715    type Value: ?Sized;
716
717    /// Applies the closure to the value, non-reactively, and returns the result,
718    /// or `None` if the value has already been disposed.
719    #[track_caller]
720    fn try_with_value<U>(
721        &self,
722        fun: impl FnOnce(&Self::Value) -> U,
723    ) -> Option<U>;
724
725    /// Applies the closure to the value, non-reactively, and returns the result.
726    ///
727    /// # Panics
728    /// Panics if you try to access a value that has been disposed.
729    #[track_caller]
730    fn with_value<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
731        self.try_with_value(fun)
732            .unwrap_or_else(unwrap_signal!(self))
733    }
734}
735
736impl<T> WithValue for T
737where
738    T: DefinedAt + ReadValue,
739{
740    type Value = <<Self as ReadValue>::Value as Deref>::Target;
741
742    fn try_with_value<U>(
743        &self,
744        fun: impl FnOnce(&Self::Value) -> U,
745    ) -> Option<U> {
746        self.try_read_value().map(|value| fun(&value))
747    }
748}
749
750/// A variation of the [`Get`] trait that provides a signposted "always-non-reactive" API.
751/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
752pub trait GetValue: DefinedAt {
753    /// The type of the value contained in the value.
754    type Value: Clone;
755
756    /// Clones and returns the value of the value, non-reactively,
757    /// or `None` if the value has already been disposed.
758    #[track_caller]
759    fn try_get_value(&self) -> Option<Self::Value>;
760
761    /// Clones and returns the value of the value, non-reactively.
762    ///
763    /// # Panics
764    /// Panics if you try to access a value that has been disposed.
765    #[track_caller]
766    fn get_value(&self) -> Self::Value {
767        self.try_get_value().unwrap_or_else(unwrap_signal!(self))
768    }
769}
770
771impl<T> GetValue for T
772where
773    T: WithValue,
774    T::Value: Clone,
775{
776    type Value = <Self as WithValue>::Value;
777
778    fn try_get_value(&self) -> Option<Self::Value> {
779        self.try_with_value(Self::Value::clone)
780    }
781}
782
783/// A variation of the [`Write`] trait that provides a signposted "always-non-reactive" API.
784/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
785pub trait WriteValue: Sized + DefinedAt {
786    /// The type of the value's value.
787    type Value: Sized + 'static;
788
789    /// Returns a non-reactive write guard, or `None` if the value has already been disposed.
790    #[track_caller]
791    fn try_write_value(&self) -> Option<UntrackedWriteGuard<Self::Value>>;
792
793    /// Returns a non-reactive write guard.
794    ///
795    /// # Panics
796    /// Panics if you try to access a value that has been disposed.
797    #[track_caller]
798    fn write_value(&self) -> UntrackedWriteGuard<Self::Value> {
799        self.try_write_value().unwrap_or_else(unwrap_signal!(self))
800    }
801}
802
803/// A variation of the [`Update`] trait that provides a signposted "always-non-reactive" API.
804/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
805pub trait UpdateValue: DefinedAt {
806    /// The type of the value contained in the value.
807    type Value;
808
809    /// Updates the value, returning the value that is
810    /// returned by the update function, or `None` if the value has already been disposed.
811    #[track_caller]
812    fn try_update_value<U>(
813        &self,
814        fun: impl FnOnce(&mut Self::Value) -> U,
815    ) -> Option<U>;
816
817    /// Updates the value.
818    #[track_caller]
819    fn update_value(&self, fun: impl FnOnce(&mut Self::Value)) {
820        self.try_update_value(fun);
821    }
822}
823
824impl<T> UpdateValue for T
825where
826    T: WriteValue,
827{
828    type Value = <Self as WriteValue>::Value;
829
830    #[track_caller]
831    fn try_update_value<U>(
832        &self,
833        fun: impl FnOnce(&mut Self::Value) -> U,
834    ) -> Option<U> {
835        let mut guard = self.try_write_value()?;
836        Some(fun(&mut *guard))
837    }
838}
839
840/// A variation of the [`Set`] trait that provides a signposted "always-non-reactive" API.
841/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
842pub trait SetValue: DefinedAt {
843    /// The type of the value contained in the value.
844    type Value;
845
846    /// Updates the value by replacing it, non-reactively.
847    ///
848    /// If the value has already been disposed, returns `Some(value)` with the value that was
849    /// passed in. Otherwise, returns `None`.
850    #[track_caller]
851    fn try_set_value(&self, value: Self::Value) -> Option<Self::Value>;
852
853    /// Updates the value by replacing it, non-reactively.
854    #[track_caller]
855    fn set_value(&self, value: Self::Value) {
856        self.try_set_value(value);
857    }
858}
859
860impl<T> SetValue for T
861where
862    T: WriteValue,
863{
864    type Value = <Self as WriteValue>::Value;
865
866    fn try_set_value(&self, value: Self::Value) -> Option<Self::Value> {
867        // Unlike most other traits, for these None actually means success:
868        if let Some(mut guard) = self.try_write_value() {
869            *guard = value;
870            None
871        } else {
872            Some(value)
873        }
874    }
875}