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}