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