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
233/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
234/// signal's subscribers will be notified.
235pub trait Write: Sized + DefinedAt + Notify {
236    /// The type of the signal's value.
237    type Value: Sized + 'static;
238
239    /// Returns the guard, or `None` if the signal has already been disposed.
240    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>>;
241
242    // Returns a guard that will not notify subscribers when dropped,
243    /// or `None` if the signal has already been disposed.
244    fn try_write_untracked(
245        &self,
246    ) -> Option<impl DerefMut<Target = Self::Value>>;
247
248    /// Returns the guard.
249    ///
250    /// # Panics
251    /// Panics if you try to access a signal that has been disposed.
252    fn write(&self) -> impl UntrackableGuard<Target = Self::Value> {
253        self.try_write().unwrap_or_else(unwrap_signal!(self))
254    }
255
256    /// Returns a guard that will not notify subscribers when dropped.
257    ///
258    /// # Panics
259    /// Panics if you try to access a signal that has been disposed.
260    fn write_untracked(&self) -> impl DerefMut<Target = Self::Value> {
261        self.try_write_untracked()
262            .unwrap_or_else(unwrap_signal!(self))
263    }
264}
265
266/// Give read-only access to a signal's value by reference inside a closure,
267/// without tracking the value reactively.
268pub trait WithUntracked: DefinedAt {
269    /// The type of the value contained in the signal.
270    type Value: ?Sized;
271
272    /// Applies the closure to the value, and returns the result,
273    /// or `None` if the signal has already been disposed.
274    #[track_caller]
275    fn try_with_untracked<U>(
276        &self,
277        fun: impl FnOnce(&Self::Value) -> U,
278    ) -> Option<U>;
279
280    /// Applies the closure to the value, and returns the result.
281    ///
282    /// # Panics
283    /// Panics if you try to access a signal that has been disposed.
284    #[track_caller]
285    fn with_untracked<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
286        self.try_with_untracked(fun)
287            .unwrap_or_else(unwrap_signal!(self))
288    }
289}
290
291impl<T> WithUntracked for T
292where
293    T: DefinedAt + ReadUntracked,
294{
295    type Value = <<Self as ReadUntracked>::Value as Deref>::Target;
296
297    fn try_with_untracked<U>(
298        &self,
299        fun: impl FnOnce(&Self::Value) -> U,
300    ) -> Option<U> {
301        self.try_read_untracked().map(|value| fun(&value))
302    }
303}
304
305/// Give read-only access to a signal's value by reference inside a closure,
306/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
307pub trait With: DefinedAt {
308    /// The type of the value contained in the signal.
309    type Value: ?Sized;
310
311    /// Subscribes to the signal, applies the closure to the value, and returns the result,
312    /// or `None` if the signal has already been disposed.
313    #[track_caller]
314    fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U>;
315
316    /// Subscribes to the signal, applies the closure to the value, and returns the result.
317    ///
318    /// # Panics
319    /// Panics if you try to access a signal that has been disposed.
320    #[track_caller]
321    fn with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
322        self.try_with(fun).unwrap_or_else(unwrap_signal!(self))
323    }
324}
325
326impl<T> With for T
327where
328    T: Read,
329{
330    type Value = <<T as Read>::Value as Deref>::Target;
331
332    #[track_caller]
333    fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U> {
334        self.try_read().map(|val| fun(&val))
335    }
336}
337
338/// Clones the value of the signal, without tracking the value reactively.
339pub trait GetUntracked: DefinedAt {
340    /// The type of the value contained in the signal.
341    type Value;
342
343    /// Clones and returns the value of the signal,
344    /// or `None` if the signal has already been disposed.
345    #[track_caller]
346    fn try_get_untracked(&self) -> Option<Self::Value>;
347
348    /// Clones and returns the value of the signal,
349    ///
350    /// # Panics
351    /// Panics if you try to access a signal that has been disposed.
352    #[track_caller]
353    fn get_untracked(&self) -> Self::Value {
354        self.try_get_untracked()
355            .unwrap_or_else(unwrap_signal!(self))
356    }
357}
358
359impl<T> GetUntracked for T
360where
361    T: WithUntracked,
362    T::Value: Clone,
363{
364    type Value = <Self as WithUntracked>::Value;
365
366    fn try_get_untracked(&self) -> Option<Self::Value> {
367        self.try_with_untracked(Self::Value::clone)
368    }
369}
370
371/// Clones the value of the signal, without tracking the value reactively.
372/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
373pub trait Get: DefinedAt {
374    /// The type of the value contained in the signal.
375    type Value: Clone;
376
377    /// Subscribes to the signal, then clones and returns the value of the signal,
378    /// or `None` if the signal has already been disposed.
379    #[track_caller]
380    fn try_get(&self) -> Option<Self::Value>;
381
382    /// Subscribes to the signal, then clones and returns the value of the signal.
383    ///
384    /// # Panics
385    /// Panics if you try to access a signal that has been disposed.
386    #[track_caller]
387    fn get(&self) -> Self::Value {
388        self.try_get().unwrap_or_else(unwrap_signal!(self))
389    }
390}
391
392impl<T> Get for T
393where
394    T: With,
395    T::Value: Clone,
396{
397    type Value = <T as With>::Value;
398
399    #[track_caller]
400    fn try_get(&self) -> Option<Self::Value> {
401        self.try_with(Self::Value::clone)
402    }
403}
404
405/// Notifies subscribers of a change in this signal.
406pub trait Notify {
407    /// Notifies subscribers of a change in this signal.
408    #[track_caller]
409    fn notify(&self);
410}
411
412/// Updates the value of a signal by applying a function that updates it in place,
413/// without notifying subscribers.
414pub trait UpdateUntracked: DefinedAt {
415    /// The type of the value contained in the signal.
416    type Value;
417
418    /// Updates the value by applying a function, returning the value returned by that function.
419    /// Does not notify subscribers that the signal has changed.
420    ///
421    /// # Panics
422    /// Panics if you try to update a signal that has been disposed.
423    #[track_caller]
424    fn update_untracked<U>(
425        &self,
426        fun: impl FnOnce(&mut Self::Value) -> U,
427    ) -> U {
428        self.try_update_untracked(fun)
429            .unwrap_or_else(unwrap_signal!(self))
430    }
431
432    /// Updates the value by applying a function, returning the value returned by that function,
433    /// or `None` if the signal has already been disposed.
434    /// Does not notify subscribers that the signal has changed.
435    fn try_update_untracked<U>(
436        &self,
437        fun: impl FnOnce(&mut Self::Value) -> U,
438    ) -> Option<U>;
439}
440
441impl<T> UpdateUntracked for T
442where
443    T: Write,
444{
445    type Value = <Self as Write>::Value;
446
447    #[track_caller]
448    fn try_update_untracked<U>(
449        &self,
450        fun: impl FnOnce(&mut Self::Value) -> U,
451    ) -> Option<U> {
452        let mut guard = self.try_write_untracked()?;
453        Some(fun(&mut *guard))
454    }
455}
456
457/// Updates the value of a signal by applying a function that updates it in place,
458/// notifying its subscribers that the value has changed.
459pub trait Update {
460    /// The type of the value contained in the signal.
461    type Value;
462
463    /// Updates the value of the signal and notifies subscribers.
464    #[track_caller]
465    fn update(&self, fun: impl FnOnce(&mut Self::Value)) {
466        self.try_update(fun);
467    }
468
469    /// Updates the value of the signal, but only notifies subscribers if the function
470    /// returns `true`.
471    #[track_caller]
472    fn maybe_update(&self, fun: impl FnOnce(&mut Self::Value) -> bool) {
473        self.try_maybe_update(|val| {
474            let did_update = fun(val);
475            (did_update, ())
476        });
477    }
478
479    /// Updates the value of the signal and notifies subscribers, returning the value that is
480    /// returned by the update function, or `None` if the signal has already been disposed.
481    #[track_caller]
482    fn try_update<U>(
483        &self,
484        fun: impl FnOnce(&mut Self::Value) -> U,
485    ) -> Option<U> {
486        self.try_maybe_update(|val| (true, fun(val)))
487    }
488
489    /// Updates the value of the signal, notifying subscribers if the update function returns
490    /// `(true, _)`, and returns the value returned by the update function,
491    /// or `None` if the signal has already been disposed.
492    fn try_maybe_update<U>(
493        &self,
494        fun: impl FnOnce(&mut Self::Value) -> (bool, U),
495    ) -> Option<U>;
496}
497
498impl<T> Update for T
499where
500    T: Write,
501{
502    type Value = <Self as Write>::Value;
503
504    #[track_caller]
505    fn try_maybe_update<U>(
506        &self,
507        fun: impl FnOnce(&mut Self::Value) -> (bool, U),
508    ) -> Option<U> {
509        let mut lock = self.try_write()?;
510        let (did_update, val) = fun(&mut *lock);
511        if !did_update {
512            lock.untrack();
513        }
514        drop(lock);
515        Some(val)
516    }
517}
518
519/// Updates the value of the signal by replacing it.
520pub trait Set {
521    /// The type of the value contained in the signal.
522    type Value;
523
524    /// Updates the value by replacing it, and notifies subscribers that it has changed.
525    fn set(&self, value: Self::Value);
526
527    /// Updates the value by replacing it, and notifies subscribers that it has changed.
528    ///
529    /// If the signal has already been disposed, returns `Some(value)` with the value that was
530    /// passed in. Otherwise, returns `None`.
531    fn try_set(&self, value: Self::Value) -> Option<Self::Value>;
532}
533
534impl<T> Set for T
535where
536    T: Update + IsDisposed,
537{
538    type Value = <Self as Update>::Value;
539
540    #[track_caller]
541    fn set(&self, value: Self::Value) {
542        self.try_update(|n| *n = value);
543    }
544
545    #[track_caller]
546    fn try_set(&self, value: Self::Value) -> Option<Self::Value> {
547        if self.is_disposed() {
548            Some(value)
549        } else {
550            self.set(value);
551            None
552        }
553    }
554}
555
556/// Allows converting a signal into an async [`Stream`].
557pub trait ToStream<T> {
558    /// Generates a [`Stream`] that emits the new value of the signal
559    /// whenever it changes.
560    ///
561    /// # Panics
562    /// Panics if you try to access a signal that is owned by a reactive node that has been disposed.
563    #[track_caller]
564    fn to_stream(&self) -> impl Stream<Item = T> + Send;
565}
566
567impl<S> ToStream<S::Value> for S
568where
569    S: Clone + Get + Send + Sync + 'static,
570    S::Value: Send + 'static,
571{
572    fn to_stream(&self) -> impl Stream<Item = S::Value> + Send {
573        let (tx, rx) = futures::channel::mpsc::unbounded();
574
575        let close_channel = tx.clone();
576
577        Owner::on_cleanup(move || close_channel.close_channel());
578
579        Effect::new_isomorphic({
580            let this = self.clone();
581            move |_| {
582                let _ = tx.unbounded_send(this.get());
583            }
584        });
585
586        rx
587    }
588}
589
590/// Allows creating a signal from an async [`Stream`].
591pub trait FromStream<T> {
592    /// Creates a signal that contains the latest value of the stream.
593    #[track_caller]
594    fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self;
595
596    /// Creates a signal that contains the latest value of the stream.
597    #[track_caller]
598    fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self;
599}
600
601impl<S, T> FromStream<T> for S
602where
603    S: From<ArcReadSignal<Option<T>>> + Send + Sync,
604    T: Send + Sync + 'static,
605{
606    fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self {
607        let (read, write) = arc_signal(None);
608        let mut stream = Box::pin(stream);
609        crate::spawn(async move {
610            while let Some(value) = stream.next().await {
611                write.set(Some(value));
612            }
613        });
614        read.into()
615    }
616
617    fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self {
618        let (read, write) = arc_signal(None);
619        let mut stream = Box::pin(stream);
620        Executor::spawn_local(async move {
621            while let Some(value) = stream.next().await {
622                write.set(Some(value));
623            }
624        });
625        read.into()
626    }
627}
628
629/// Checks whether a signal has already been disposed.
630pub trait IsDisposed {
631    /// If `true`, the signal cannot be accessed without a panic.
632    fn is_disposed(&self) -> bool;
633}
634
635/// Turns a signal back into a raw value.
636pub trait IntoInner {
637    /// The type of the value contained in the signal.
638    type Value;
639
640    /// Returns the inner value if this is the only reference to to the signal.
641    /// Otherwise, returns `None` and drops this reference.
642    /// # Panics
643    /// Panics if the inner lock is poisoned.
644    fn into_inner(self) -> Option<Self::Value>;
645}
646
647/// Describes where the signal was defined. This is used for diagnostic warnings and is purely a
648/// debug-mode tool.
649pub trait DefinedAt {
650    /// Returns the location at which the signal was defined. This is usually simply `None` in
651    /// release mode.
652    fn defined_at(&self) -> Option<&'static Location<'static>>;
653}
654
655#[doc(hidden)]
656pub fn panic_getting_disposed_signal(
657    defined_at: Option<&'static Location<'static>>,
658    location: &'static Location<'static>,
659) -> String {
660    if let Some(defined_at) = defined_at {
661        format!(
662            "At {location}, you tried to access a reactive value which was \
663             defined at {defined_at}, but it has already been disposed."
664        )
665    } else {
666        format!(
667            "At {location}, you tried to access a reactive value, but it has \
668             already been disposed."
669        )
670    }
671}
672
673/// A variation of the [`Read`] trait that provides a signposted "always-non-reactive" API.
674/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
675pub trait ReadValue: Sized + DefinedAt {
676    /// The guard type that will be returned, which can be dereferenced to the value.
677    type Value: Deref;
678
679    /// Returns the non-reactive guard, or `None` if the value has already been disposed.
680    #[track_caller]
681    fn try_read_value(&self) -> Option<Self::Value>;
682
683    /// Returns the non-reactive guard.
684    ///
685    /// # Panics
686    /// Panics if you try to access a value that has been disposed.
687    #[track_caller]
688    fn read_value(&self) -> Self::Value {
689        self.try_read_value().unwrap_or_else(unwrap_signal!(self))
690    }
691}
692
693/// A variation of the [`With`] trait that provides a signposted "always-non-reactive" API.
694/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
695pub trait WithValue: DefinedAt {
696    /// The type of the value contained in the value.
697    type Value: ?Sized;
698
699    /// Applies the closure to the value, non-reactively, and returns the result,
700    /// or `None` if the value has already been disposed.
701    #[track_caller]
702    fn try_with_value<U>(
703        &self,
704        fun: impl FnOnce(&Self::Value) -> U,
705    ) -> Option<U>;
706
707    /// Applies the closure to the value, non-reactively, and returns the result.
708    ///
709    /// # Panics
710    /// Panics if you try to access a value that has been disposed.
711    #[track_caller]
712    fn with_value<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
713        self.try_with_value(fun)
714            .unwrap_or_else(unwrap_signal!(self))
715    }
716}
717
718impl<T> WithValue for T
719where
720    T: DefinedAt + ReadValue,
721{
722    type Value = <<Self as ReadValue>::Value as Deref>::Target;
723
724    fn try_with_value<U>(
725        &self,
726        fun: impl FnOnce(&Self::Value) -> U,
727    ) -> Option<U> {
728        self.try_read_value().map(|value| fun(&value))
729    }
730}
731
732/// A variation of the [`Get`] trait that provides a signposted "always-non-reactive" API.
733/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
734pub trait GetValue: DefinedAt {
735    /// The type of the value contained in the value.
736    type Value: Clone;
737
738    /// Clones and returns the value of the value, non-reactively,
739    /// or `None` if the value has already been disposed.
740    #[track_caller]
741    fn try_get_value(&self) -> Option<Self::Value>;
742
743    /// Clones and returns the value of the value, non-reactively.
744    ///
745    /// # Panics
746    /// Panics if you try to access a value that has been disposed.
747    #[track_caller]
748    fn get_value(&self) -> Self::Value {
749        self.try_get_value().unwrap_or_else(unwrap_signal!(self))
750    }
751}
752
753impl<T> GetValue for T
754where
755    T: WithValue,
756    T::Value: Clone,
757{
758    type Value = <Self as WithValue>::Value;
759
760    fn try_get_value(&self) -> Option<Self::Value> {
761        self.try_with_value(Self::Value::clone)
762    }
763}
764
765/// A variation of the [`Write`] trait that provides a signposted "always-non-reactive" API.
766/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
767pub trait WriteValue: Sized + DefinedAt {
768    /// The type of the value's value.
769    type Value: Sized + 'static;
770
771    /// Returns a non-reactive write guard, or `None` if the value has already been disposed.
772    #[track_caller]
773    fn try_write_value(&self) -> Option<UntrackedWriteGuard<Self::Value>>;
774
775    /// Returns a non-reactive write guard.
776    ///
777    /// # Panics
778    /// Panics if you try to access a value that has been disposed.
779    #[track_caller]
780    fn write_value(&self) -> UntrackedWriteGuard<Self::Value> {
781        self.try_write_value().unwrap_or_else(unwrap_signal!(self))
782    }
783}
784
785/// A variation of the [`Update`] trait that provides a signposted "always-non-reactive" API.
786/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
787pub trait UpdateValue: DefinedAt {
788    /// The type of the value contained in the value.
789    type Value;
790
791    /// Updates the value, returning the value that is
792    /// returned by the update function, or `None` if the value has already been disposed.
793    #[track_caller]
794    fn try_update_value<U>(
795        &self,
796        fun: impl FnOnce(&mut Self::Value) -> U,
797    ) -> Option<U>;
798
799    /// Updates the value.
800    #[track_caller]
801    fn update_value(&self, fun: impl FnOnce(&mut Self::Value)) {
802        self.try_update_value(fun);
803    }
804}
805
806impl<T> UpdateValue for T
807where
808    T: WriteValue,
809{
810    type Value = <Self as WriteValue>::Value;
811
812    #[track_caller]
813    fn try_update_value<U>(
814        &self,
815        fun: impl FnOnce(&mut Self::Value) -> U,
816    ) -> Option<U> {
817        let mut guard = self.try_write_value()?;
818        Some(fun(&mut *guard))
819    }
820}
821
822/// A variation of the [`Set`] trait that provides a signposted "always-non-reactive" API.
823/// E.g. for [`StoredValue`](`crate::owner::StoredValue`).
824pub trait SetValue: DefinedAt {
825    /// The type of the value contained in the value.
826    type Value;
827
828    /// Updates the value by replacing it, non-reactively.
829    ///
830    /// If the value has already been disposed, returns `Some(value)` with the value that was
831    /// passed in. Otherwise, returns `None`.
832    #[track_caller]
833    fn try_set_value(&self, value: Self::Value) -> Option<Self::Value>;
834
835    /// Updates the value by replacing it, non-reactively.
836    #[track_caller]
837    fn set_value(&self, value: Self::Value) {
838        self.try_set_value(value);
839    }
840}
841
842impl<T> SetValue for T
843where
844    T: WriteValue,
845{
846    type Value = <Self as WriteValue>::Value;
847
848    fn try_set_value(&self, value: Self::Value) -> Option<Self::Value> {
849        // Unlike most other traits, for these None actually means success:
850        if let Some(mut guard) = self.try_write_value() {
851            *guard = value;
852            None
853        } else {
854            Some(value)
855        }
856    }
857}