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}