dioxus_signals/read.rs
1use std::collections::{HashMap, HashSet};
2use std::{
3 mem::MaybeUninit,
4 ops::{Deref, Index},
5};
6
7use crate::{ext_methods, MappedSignal, ReadSignal};
8use dioxus_core::Subscribers;
9use generational_box::{AnyStorage, UnsyncStorage};
10
11/// A reference to a value that can be read from.
12#[allow(type_alias_bounds)]
13pub type ReadableRef<'a, T: Readable, O = <T as Readable>::Target> =
14 <T::Storage as AnyStorage>::Ref<'a, O>;
15
16/// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadSignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.
17///
18/// # Example
19/// ```rust
20/// # use dioxus::prelude::*;
21/// fn double(something_readable: &impl Readable<Target = i32>) -> i32 {
22/// something_readable.cloned() * 2
23/// }
24///
25/// static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
26///
27/// fn MyComponent(count: Signal<i32>) -> Element {
28/// // Since we defined the function in terms of the readable trait, we can use it with any readable type (Signal, GlobalSignal, ReadSignal, etc)
29/// let doubled = use_memo(move || double(&count));
30/// let global_count_doubled = use_memo(|| double(&COUNT));
31/// rsx! {
32/// div {
33/// "Count local: {count}"
34/// "Doubled local: {doubled}"
35/// "Count global: {COUNT}"
36/// "Doubled global: {global_count_doubled}"
37/// }
38/// }
39/// }
40/// ```
41pub trait Readable {
42 /// The target type of the reference.
43 type Target: ?Sized;
44
45 /// The type of the storage this readable uses.
46 type Storage: AnyStorage;
47
48 /// Try to get a reference to the value without checking the lifetime. This will subscribe the current scope to the signal.
49 ///
50 /// NOTE: This method is completely safe because borrow checking is done at runtime.
51 fn try_read_unchecked(
52 &self,
53 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
54 where
55 Self::Target: 'static;
56
57 /// Try to peek the current value of the signal without subscribing to updates. If the value has
58 /// been dropped, this will return an error.
59 ///
60 /// NOTE: This method is completely safe because borrow checking is done at runtime.
61 fn try_peek_unchecked(
62 &self,
63 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
64 where
65 Self::Target: 'static;
66
67 /// Get the underlying subscriber list for this readable. This is used to track when the value changes and notify subscribers.
68 fn subscribers(&self) -> Subscribers
69 where
70 Self::Target: 'static;
71}
72
73/// An extension trait for `Readable` types that provides some convenience methods.
74pub trait ReadableExt: Readable {
75 /// Get the current value of the state. If this is a signal, this will subscribe the current scope to the signal.
76 /// If the value has been dropped, this will panic. Calling this on a Signal is the same as
77 /// using the signal() syntax to read and subscribe to its value
78 #[track_caller]
79 fn read(&self) -> ReadableRef<'_, Self>
80 where
81 Self::Target: 'static,
82 {
83 self.try_read().unwrap()
84 }
85
86 /// Try to get the current value of the state. If this is a signal, this will subscribe the current scope to the signal.
87 #[track_caller]
88 fn try_read(&self) -> Result<ReadableRef<'_, Self>, generational_box::BorrowError>
89 where
90 Self::Target: 'static,
91 {
92 self.try_read_unchecked()
93 .map(Self::Storage::downcast_lifetime_ref)
94 }
95
96 /// Get a reference to the value without checking the lifetime. This will subscribe the current scope to the signal.
97 ///
98 /// NOTE: This method is completely safe because borrow checking is done at runtime.
99 #[track_caller]
100 fn read_unchecked(&self) -> ReadableRef<'static, Self>
101 where
102 Self::Target: 'static,
103 {
104 self.try_read_unchecked().unwrap()
105 }
106
107 /// Get the current value of the state without subscribing to updates. If the value has been dropped, this will panic.
108 ///
109 /// # Example
110 /// ```rust
111 /// # use dioxus::prelude::*;
112 /// fn MyComponent(mut count: Signal<i32>) -> Element {
113 /// let mut event_source = use_signal(|| None);
114 /// let doubled = use_memo(move || {
115 /// // We want to log the value of the event_source, but we don't need to rerun the doubled value if the event_source changes (because the value of doubled doesn't depend on the event_source)
116 /// // We can read the value with peek without subscribing to updates
117 /// let source = event_source.peek();
118 /// tracing::info!("Clicked: {source:?}");
119 /// count() * 2
120 /// });
121 /// rsx! {
122 /// div { "Count: {count}" }
123 /// div { "Doubled: {doubled}" }
124 /// button {
125 /// onclick: move |_| {
126 /// event_source.set(Some("Click me button"));
127 /// count += 1;
128 /// },
129 /// "Click me"
130 /// }
131 /// button {
132 /// onclick: move |_| {
133 /// event_source.set(Some("Double me button"));
134 /// count += 1;
135 /// },
136 /// "Double me"
137 /// }
138 /// }
139 /// }
140 /// ```
141 #[track_caller]
142 fn peek(&self) -> ReadableRef<'_, Self>
143 where
144 Self::Target: 'static,
145 {
146 Self::Storage::downcast_lifetime_ref(self.peek_unchecked())
147 }
148
149 /// Try to peek the current value of the signal without subscribing to updates. If the value has
150 /// been dropped, this will return an error.
151 #[track_caller]
152 fn try_peek(&self) -> Result<ReadableRef<'_, Self>, generational_box::BorrowError>
153 where
154 Self::Target: 'static,
155 {
156 self.try_peek_unchecked()
157 .map(Self::Storage::downcast_lifetime_ref)
158 }
159
160 /// Get the current value of the signal without checking the lifetime. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**
161 ///
162 /// If the signal has been dropped, this will panic.
163 #[track_caller]
164 fn peek_unchecked(&self) -> ReadableRef<'static, Self>
165 where
166 Self::Target: 'static,
167 {
168 self.try_peek_unchecked().unwrap()
169 }
170
171 /// Map the references of the readable value to a new type. This lets you provide a view
172 /// into the readable value without creating a new signal or cloning the value.
173 ///
174 /// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not
175 /// change. If you want to memorize the view, you can use a [`crate::Memo`] instead. For fine grained scoped updates, use
176 /// stores instead
177 ///
178 /// # Example
179 /// ```rust
180 /// # use dioxus::prelude::*;
181 /// fn List(list: Signal<Vec<i32>>) -> Element {
182 /// rsx! {
183 /// for index in 0..list.len() {
184 /// // We can use the `map` method to provide a view into the single item in the list that the child component will render
185 /// Item { item: list.map(move |v| &v[index]) }
186 /// }
187 /// }
188 /// }
189 ///
190 /// // The child component doesn't need to know that the mapped value is coming from a list
191 /// #[component]
192 /// fn Item(item: ReadSignal<i32>) -> Element {
193 /// rsx! {
194 /// div { "Item: {item}" }
195 /// }
196 /// }
197 /// ```
198 fn map<F, O>(self, f: F) -> MappedSignal<O, Self, F>
199 where
200 Self: Clone + Sized,
201 F: Fn(&Self::Target) -> &O,
202 {
203 MappedSignal::new(self, f)
204 }
205
206 /// Clone the inner value and return it. If the value has been dropped, this will panic.
207 #[track_caller]
208 fn cloned(&self) -> Self::Target
209 where
210 Self::Target: Clone + 'static,
211 {
212 self.read().clone()
213 }
214
215 /// Run a function with a reference to the value. If the value has been dropped, this will panic.
216 #[track_caller]
217 fn with<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O
218 where
219 Self::Target: 'static,
220 {
221 f(&*self.read())
222 }
223
224 /// Run a function with a reference to the value. If the value has been dropped, this will panic.
225 #[track_caller]
226 fn with_peek<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O
227 where
228 Self::Target: 'static,
229 {
230 f(&*self.peek())
231 }
232
233 /// Index into the inner value and return a reference to the result. If the value has been dropped or the index is invalid, this will panic.
234 #[track_caller]
235 fn index<I>(
236 &self,
237 index: I,
238 ) -> ReadableRef<'_, Self, <Self::Target as std::ops::Index<I>>::Output>
239 where
240 Self::Target: std::ops::Index<I> + 'static,
241 {
242 <Self::Storage as AnyStorage>::map(self.read(), |v| v.index(index))
243 }
244
245 /// SAFETY: You must call this function directly with `self` as the argument.
246 /// This function relies on the size of the object you return from the deref
247 /// being the same as the object you pass in
248 #[doc(hidden)]
249 unsafe fn deref_impl<'a>(&self) -> &'a dyn Fn() -> Self::Target
250 where
251 Self: Sized + 'a,
252 Self::Target: Clone + 'static,
253 {
254 // https://github.com/dtolnay/case-studies/tree/master/callable-types
255
256 // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
257 let uninit_callable = MaybeUninit::<Self>::uninit();
258 // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
259 let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
260
261 // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
262 let size_of_closure = std::mem::size_of_val(&uninit_closure);
263 assert_eq!(size_of_closure, std::mem::size_of::<Self>());
264
265 // Then cast the lifetime of the closure to the lifetime of &self.
266 fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
267 b
268 }
269 let reference_to_closure = cast_lifetime(
270 {
271 // The real closure that we will never use.
272 &uninit_closure
273 },
274 #[allow(clippy::missing_transmute_annotations)]
275 // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
276 unsafe {
277 std::mem::transmute(self)
278 },
279 );
280
281 // Cast the closure to a trait object.
282 reference_to_closure as &_
283 }
284}
285
286impl<R: Readable + ?Sized> ReadableExt for R {}
287
288/// An extension trait for `Readable` types that can be boxed into a trait object.
289pub trait ReadableBoxExt: Readable<Storage = UnsyncStorage> {
290 /// Box the readable value into a trait object. This is useful for passing around readable values without knowing their concrete type.
291 fn boxed(self) -> ReadSignal<Self::Target>
292 where
293 Self: Sized + 'static,
294 {
295 ReadSignal::new(self)
296 }
297}
298impl<R: Readable<Storage = UnsyncStorage> + ?Sized> ReadableBoxExt for R {}
299
300/// An extension trait for `Readable<Vec<T>>` that provides some convenience methods.
301pub trait ReadableVecExt<T>: Readable<Target = Vec<T>> {
302 /// Returns the length of the inner vector.
303 #[track_caller]
304 fn len(&self) -> usize
305 where
306 T: 'static,
307 {
308 self.with(|v| v.len())
309 }
310
311 /// Returns true if the inner vector is empty.
312 #[track_caller]
313 fn is_empty(&self) -> bool
314 where
315 T: 'static,
316 {
317 self.with(|v| v.is_empty())
318 }
319
320 /// Get the first element of the inner vector.
321 #[track_caller]
322 fn first(&self) -> Option<ReadableRef<'_, Self, T>>
323 where
324 T: 'static,
325 {
326 <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.first())
327 }
328
329 /// Get the last element of the inner vector.
330 #[track_caller]
331 fn last(&self) -> Option<ReadableRef<'_, Self, T>>
332 where
333 T: 'static,
334 {
335 <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.last())
336 }
337
338 /// Get the element at the given index of the inner vector.
339 #[track_caller]
340 fn get(&self, index: usize) -> Option<ReadableRef<'_, Self, T>>
341 where
342 T: 'static,
343 {
344 <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.get(index))
345 }
346
347 /// Get an iterator over the values of the inner vector.
348 #[track_caller]
349 fn iter(&self) -> ReadableValueIterator<'_, Self>
350 where
351 Self: Sized,
352 {
353 ReadableValueIterator {
354 index: 0,
355 value: self,
356 }
357 }
358}
359
360/// An iterator over the values of a `Readable<Vec<T>>`.
361pub struct ReadableValueIterator<'a, R> {
362 index: usize,
363 value: &'a R,
364}
365
366impl<'a, T: 'static, R: Readable<Target = Vec<T>>> Iterator for ReadableValueIterator<'a, R> {
367 type Item = ReadableRef<'a, R, T>;
368
369 fn next(&mut self) -> Option<Self::Item> {
370 let index = self.index;
371 self.index += 1;
372 self.value.get(index)
373 }
374}
375
376impl<T, R> ReadableVecExt<T> for R where R: Readable<Target = Vec<T>> {}
377
378/// An extension trait for `Readable<Option<T>>` that provides some convenience methods.
379pub trait ReadableOptionExt<T>: Readable<Target = Option<T>> {
380 /// Unwraps the inner value and clones it.
381 #[track_caller]
382 fn unwrap(&self) -> T
383 where
384 T: Clone + 'static,
385 {
386 self.as_ref().unwrap().clone()
387 }
388
389 /// Attempts to read the inner value of the Option.
390 #[track_caller]
391 fn as_ref(&self) -> Option<ReadableRef<'_, Self, T>>
392 where
393 T: 'static,
394 {
395 <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.as_ref())
396 }
397}
398
399impl<T, R> ReadableOptionExt<T> for R where R: Readable<Target = Option<T>> {}
400
401/// An extension trait for `Readable<Option<T>>` that provides some convenience methods.
402pub trait ReadableResultExt<T, E>: Readable<Target = Result<T, E>> {
403 /// Unwraps the inner value and clones it.
404 #[track_caller]
405 fn unwrap(&self) -> T
406 where
407 T: Clone + 'static,
408 E: 'static,
409 {
410 self.as_ref()
411 .unwrap_or_else(|_| panic!("Tried to unwrap a Result that was an error"))
412 .clone()
413 }
414
415 /// Attempts to read the inner value of the Option.
416 #[track_caller]
417 fn as_ref(&self) -> Result<ReadableRef<'_, Self, T>, ReadableRef<'_, Self, E>>
418 where
419 T: 'static,
420 E: 'static,
421 {
422 <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.as_ref().ok()).ok_or(
423 <Self::Storage as AnyStorage>::map(self.read(), |v| v.as_ref().err().unwrap()),
424 )
425 }
426}
427
428impl<T, E, R> ReadableResultExt<T, E> for R where R: Readable<Target = Result<T, E>> {}
429
430/// An extension trait for [`Readable<String>`] that provides some convenience methods.
431pub trait ReadableStringExt: Readable<Target = String> {
432 ext_methods! {
433 /// Check the capacity of the string.
434 fn capacity(&self) -> usize = String::capacity;
435 }
436}
437
438impl<W> ReadableStringExt for W where W: Readable<Target = String> {}
439
440/// An extension trait for [`Readable<String>`] and [`Readable<str>`] that provides some convenience methods.
441pub trait ReadableStrExt: Readable<Target: Deref<Target = str> + 'static> {
442 ext_methods! {
443 /// Check if the string is empty.
444 fn is_empty(&self) -> bool = |s: &Self::Target| s.deref().is_empty();
445
446 /// Get the length of the string.
447 fn len(&self) -> usize = |s: &Self::Target| s.deref().len();
448
449 /// Check if the string contains the given pattern.
450 fn contains(&self, pat: &str) -> bool = |s: &Self::Target, pat| s.deref().contains(pat);
451 }
452}
453
454impl<W> ReadableStrExt for W where W: Readable<Target: Deref<Target = str> + 'static> {}
455
456/// An extension trait for [`Readable<HashMap<K, V, H>>`] that provides some convenience methods.
457pub trait ReadableHashMapExt<K: 'static, V: 'static, H: 'static>:
458 Readable<Target = HashMap<K, V, H>>
459{
460 ext_methods! {
461 /// Check if the hashmap is empty.
462 fn is_empty(&self) -> bool = HashMap::is_empty;
463
464 /// Get the length of the hashmap.
465 fn len(&self) -> usize = HashMap::len;
466
467 /// Get the capacity of the hashmap.
468 fn capacity(&self) -> usize = HashMap::capacity;
469 }
470
471 /// Get the value for the given key.
472 #[track_caller]
473 fn get(&self, key: &K) -> Option<ReadableRef<'_, Self, V>>
474 where
475 K: std::hash::Hash + Eq,
476 H: std::hash::BuildHasher,
477 {
478 <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.get(key))
479 }
480
481 /// Check if the hashmap contains the given key.
482 #[track_caller]
483 fn contains_key(&self, key: &K) -> bool
484 where
485 K: std::hash::Hash + Eq,
486 H: std::hash::BuildHasher,
487 {
488 self.with(|v| v.contains_key(key))
489 }
490}
491
492impl<K: 'static, V: 'static, H: 'static, R> ReadableHashMapExt<K, V, H> for R where
493 R: Readable<Target = HashMap<K, V, H>>
494{
495}
496
497/// An extension trait for [`Readable<HashSet<V, H>>`] that provides some convenience methods.
498pub trait ReadableHashSetExt<V: 'static, H: 'static>: Readable<Target = HashSet<V, H>> {
499 ext_methods! {
500 /// Check if the hashset is empty.
501 fn is_empty(&self) -> bool = HashSet::is_empty;
502
503 /// Get the length of the hashset.
504 fn len(&self) -> usize = HashSet::len;
505
506 /// Get the capacity of the hashset.
507 fn capacity(&self) -> usize = HashSet::capacity;
508 }
509
510 /// Check if the hashset contains the given value.
511 #[track_caller]
512 fn contains(&self, value: &V) -> bool
513 where
514 V: std::hash::Hash + Eq,
515 H: std::hash::BuildHasher,
516 {
517 self.with(|v| v.contains(value))
518 }
519}
520
521impl<V: 'static, H: 'static, R> ReadableHashSetExt<V, H> for R where
522 R: Readable<Target = HashSet<V, H>>
523{
524}