diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 5e20e30c2..9f3b1da5d 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -80,7 +80,7 @@ fn BreedPic(breed: Signal) -> Element { .await }); - match fut.read().as_ref() { + match fut.read_unchecked().as_ref() { Some(Ok(resp)) => rsx! { button { onclick: move |_| fut.restart(), "Click to fetch another doggo" } img { max_width: "500px", max_height: "500px", src: "{resp.message}" } diff --git a/examples/suspense.rs b/examples/suspense.rs index 05c651dea..7a26dd5b6 100644 --- a/examples/suspense.rs +++ b/examples/suspense.rs @@ -62,7 +62,7 @@ fn Doggo() -> Element { .await }); - match fut.read().as_ref() { + match fut.read_unchecked().as_ref() { Some(Ok(resp)) => rsx! { button { onclick: move |_| fut.restart(), "Click to fetch another doggo" } div { img { max_width: "500px", max_height: "500px", src: "{resp.message}" } } diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 341ef89bf..296e1741c 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -98,7 +98,7 @@ impl> GenerationalBox { /// Try to read the value. Returns None if the value is no longer valid. #[track_caller] - pub fn try_read(&self) -> Result, BorrowError> { + pub fn try_read(&self) -> Result, BorrowError> { if !self.validate() { return Err(BorrowError::Dropped(ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -129,13 +129,13 @@ impl> GenerationalBox { /// Read the value. Panics if the value is no longer valid. #[track_caller] - pub fn read(&self) -> S::Ref { + pub fn read(&self) -> S::Ref<'static, T> { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. #[track_caller] - pub fn try_write(&self) -> Result, BorrowMutError> { + pub fn try_write(&self) -> Result, BorrowMutError> { if !self.validate() { return Err(BorrowMutError::Dropped(ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -162,7 +162,7 @@ impl> GenerationalBox { /// Write the value. Panics if the value is no longer valid. #[track_caller] - pub fn write(&self) -> S::Mut { + pub fn write(&self) -> S::Mut<'static, T> { self.try_write().unwrap() } @@ -210,13 +210,13 @@ pub trait Storage: AnyStorage + 'static { fn try_read( &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, - ) -> Result, BorrowError>; + ) -> Result, BorrowError>; /// Try to write the value. Returns None if the value is no longer valid. fn try_write( &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, - ) -> Result, BorrowMutError>; + ) -> Result, BorrowMutError>; /// Set the value fn set(&'static self, value: Data); @@ -228,35 +228,45 @@ pub trait Storage: AnyStorage + 'static { /// A trait for any storage backing type. pub trait AnyStorage: Default { /// The reference this storage type returns. - type Ref: Deref + 'static; + type Ref<'a, T: ?Sized + 'static>: Deref; /// The mutable reference this storage type returns. - type Mut: DerefMut + 'static; + type Mut<'a, T: ?Sized + 'static>: DerefMut; + + /// Downcast a reference in a Ref to a more specific lifetime + /// + /// This function enforces the variance of the lifetime parameter `'a` in Ref. + fn downcast_ref<'a: 'b, 'b, T: ?Sized + 'static>(ref_: Self::Ref<'a, T>) -> Self::Ref<'b, T>; + + /// Downcast a mutable reference in a RefMut to a more specific lifetime + /// + /// This function enforces the variance of the lifetime parameter `'a` in Ref. + fn downcast_mut<'a: 'b, 'b, T: ?Sized + 'static>(mut_: Self::Mut<'a, T>) -> Self::Mut<'b, T>; /// Try to map the mutable ref. - fn try_map_mut( - mut_ref: Self::Mut, + fn try_map_mut( + mut_ref: Self::Mut<'_, T>, f: impl FnOnce(&mut T) -> Option<&mut U>, - ) -> Option>; + ) -> Option>; /// Map the mutable ref. - fn map_mut( - mut_ref: Self::Mut, + fn map_mut( + mut_ref: Self::Mut<'_, T>, f: impl FnOnce(&mut T) -> &mut U, - ) -> Self::Mut { + ) -> Self::Mut<'_, U> { Self::try_map_mut(mut_ref, |v| Some(f(v))).unwrap() } /// Try to map the ref. fn try_map( - ref_: Self::Ref, + ref_: Self::Ref<'_, T>, f: impl FnOnce(&T) -> Option<&U>, - ) -> Option>; + ) -> Option>; /// Map the ref. fn map( - ref_: Self::Ref, + ref_: Self::Ref<'_, T>, f: impl FnOnce(&T) -> &U, - ) -> Self::Ref { + ) -> Self::Ref<'_, U> { Self::try_map(ref_, |v| Some(f(v))).unwrap() } diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 1edd9a12e..74643f40e 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -20,13 +20,21 @@ fn sync_runtime() -> &'static Arc>>> { } impl AnyStorage for SyncStorage { - type Ref = GenerationalRef>; - type Mut = GenerationalRefMut>; + type Ref<'a, R: ?Sized + 'static> = GenerationalRef>; + type Mut<'a, W: ?Sized + 'static> = GenerationalRefMut>; - fn try_map( - ref_: Self::Ref, + fn downcast_ref<'a: 'b, 'b, T: ?Sized + 'static>(ref_: Self::Ref<'a, T>) -> Self::Ref<'b, T> { + ref_ + } + + fn downcast_mut<'a: 'b, 'b, T: ?Sized + 'static>(mut_: Self::Mut<'a, T>) -> Self::Mut<'b, T> { + mut_ + } + + fn try_map( + ref_: Self::Ref<'_, I>, f: impl FnOnce(&I) -> Option<&U>, - ) -> Option> { + ) -> Option> { let GenerationalRef { inner, #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -46,10 +54,10 @@ impl AnyStorage for SyncStorage { }) } - fn try_map_mut( - mut_ref: Self::Mut, + fn try_map_mut( + mut_ref: Self::Mut<'_, I>, f: impl FnOnce(&mut I) -> Option<&mut U>, - ) -> Option> { + ) -> Option> { let GenerationalRefMut { inner, #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -101,7 +109,7 @@ impl Storage for SyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefBorrowInfo, - ) -> Result, error::BorrowError> { + ) -> Result, error::BorrowError> { let read = self.0.try_read(); #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -132,7 +140,7 @@ impl Storage for SyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefMutBorrowInfo, - ) -> Result, error::BorrowMutError> { + ) -> Result, error::BorrowMutError> { let write = self.0.try_write(); #[cfg(any(debug_assertions, feature = "debug_ownership"))] diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index da08cf4c4..753940b82 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -15,7 +15,7 @@ impl Storage for UnsyncStorage { #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefBorrowInfo, - ) -> Result, error::BorrowError> { + ) -> Result, error::BorrowError> { let borrow = self.0.try_borrow(); #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -46,7 +46,7 @@ impl Storage for UnsyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefMutBorrowInfo, - ) -> Result, error::BorrowMutError> { + ) -> Result, error::BorrowMutError> { let borrow = self.0.try_borrow_mut(); #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -89,13 +89,21 @@ thread_local! { } impl AnyStorage for UnsyncStorage { - type Ref = GenerationalRef>; - type Mut = GenerationalRefMut>; + type Ref<'a, R: ?Sized + 'static> = GenerationalRef>; + type Mut<'a, W: ?Sized + 'static> = GenerationalRefMut>; - fn try_map( - _self: Self::Ref, + fn downcast_ref<'a: 'b, 'b, T: ?Sized + 'static>(ref_: Self::Ref<'a, T>) -> Self::Ref<'b, T> { + ref_ + } + + fn downcast_mut<'a: 'b, 'b, T: ?Sized + 'static>(mut_: Self::Mut<'a, T>) -> Self::Mut<'b, T> { + mut_ + } + + fn try_map( + _self: Self::Ref<'_, I>, f: impl FnOnce(&I) -> Option<&U>, - ) -> Option> { + ) -> Option> { let GenerationalRef { inner, #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -109,10 +117,10 @@ impl AnyStorage for UnsyncStorage { }) } - fn try_map_mut( - mut_ref: Self::Mut, + fn try_map_mut( + mut_ref: Self::Mut<'_, I>, f: impl FnOnce(&mut I) -> Option<&mut U>, - ) -> Option> { + ) -> Option> { let GenerationalRefMut { inner, #[cfg(any(debug_assertions, feature = "debug_borrows"))] diff --git a/packages/router/src/contexts/router.rs b/packages/router/src/contexts/router.rs index f4c8bcaea..85f5ac9ed 100644 --- a/packages/router/src/contexts/router.rs +++ b/packages/router/src/contexts/router.rs @@ -157,7 +157,7 @@ impl RouterContext { /// Will fail silently if there is no previous location to go to. pub fn go_back(&self) { { - self.inner.clone().write().history.go_back(); + self.inner.write_unchecked().history.go_back(); } self.change_route(); @@ -168,7 +168,7 @@ impl RouterContext { /// Will fail silently if there is no next location to go to. pub fn go_forward(&self) { { - self.inner.clone().write().history.go_forward(); + self.inner.write_unchecked().history.go_forward(); } self.change_route(); @@ -179,7 +179,7 @@ impl RouterContext { target: NavigationTarget>, ) -> Option { { - let mut write = self.inner.clone().write(); + let mut write = self.inner.write_unchecked(); match target { NavigationTarget::Internal(p) => write.history.push(p), NavigationTarget::External(e) => return write.external(e), @@ -195,7 +195,7 @@ impl RouterContext { pub fn push(&self, target: impl Into) -> Option { let target = self.resolve_into_routable(target.into()); { - let mut write = self.inner.clone().write(); + let mut write = self.inner.write_unchecked(); match target { NavigationTarget::Internal(p) => write.history.push(p), NavigationTarget::External(e) => return write.external(e), @@ -212,7 +212,7 @@ impl RouterContext { let target = self.resolve_into_routable(target.into()); { - let mut state = self.inner.clone().write(); + let mut state = self.inner.write_unchecked(); match target { NavigationTarget::Internal(p) => state.history.replace(p), NavigationTarget::External(e) => return state.external(e), @@ -276,14 +276,14 @@ impl RouterContext { /// Clear any unresolved errors pub fn clear_error(&self) { - let mut write_inner = self.inner.clone().write(); + let mut write_inner = self.inner.write_unchecked(); write_inner.unresolved_error = None; write_inner.update_subscribers(); } pub(crate) fn render_error(&self) -> Element { - let inner_read = self.inner.clone().write(); + let inner_read = self.inner.write_unchecked(); inner_read .unresolved_error .as_ref() @@ -297,7 +297,7 @@ impl RouterContext { let callback = callback.clone(); drop(self_read); if let Some(new) = callback(myself) { - let mut self_write = self.inner.clone().write(); + let mut self_write = self.inner.write_unchecked(); match new { NavigationTarget::Internal(p) => self_write.history.replace(p), NavigationTarget::External(e) => return self_write.external(e), diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index cb37d980f..ac0fc0805 100644 --- a/packages/signals/src/copy_value.rs +++ b/packages/signals/src/copy_value.rs @@ -15,6 +15,7 @@ use generational_box::{GenerationalBox, Owner, Storage}; use crate::ReadableRef; use crate::Writable; +use crate::WritableRef; use crate::{ReactiveContext, Readable}; /// Run a closure with the given owner. @@ -211,40 +212,43 @@ impl> Readable for CopyValue { type Target = T; type Storage = S; - fn try_read(&self) -> Result, generational_box::BorrowError> { + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { self.value.try_read() } - fn peek(&self) -> ReadableRef { + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { self.value.read() } } impl> Writable for CopyValue { - type Mut = S::Mut; + type Mut<'a, R: ?Sized + 'static> = S::Mut<'a, R>; fn map_mut &mut U>( - mut_: Self::Mut, + mut_: Self::Mut<'_, I>, f: F, - ) -> Self::Mut { + ) -> Self::Mut<'_, U> { S::map_mut(mut_, f) } fn try_map_mut Option<&mut U>>( - mut_: Self::Mut, + mut_: Self::Mut<'_, I>, f: F, - ) -> Option> { + ) -> Option> { S::try_map_mut(mut_, f) } - #[track_caller] - fn try_write(&mut self) -> Result, generational_box::BorrowMutError> { - self.value.try_write() + fn downcast_mut<'a: 'b, 'b, R: ?Sized + 'static>(mut_: Self::Mut<'a, R>) -> Self::Mut<'b, R> { + S::downcast_mut(mut_) } #[track_caller] - fn write(&mut self) -> Self::Mut { - self.value.write() + fn try_write_unchecked( + &self, + ) -> Result, generational_box::BorrowMutError> { + self.value.try_write() } #[track_caller] diff --git a/packages/signals/src/global/memo.rs b/packages/signals/src/global/memo.rs index 61d1c10f4..67e9544fc 100644 --- a/packages/signals/src/global/memo.rs +++ b/packages/signals/src/global/memo.rs @@ -56,13 +56,15 @@ impl Readable for GlobalMemo { type Storage = UnsyncStorage; #[track_caller] - fn try_read(&self) -> Result, generational_box::BorrowError> { - self.memo().try_read() + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + self.memo().try_read_unchecked() } #[track_caller] - fn peek(&self) -> ReadableRef { - self.memo().peek() + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { + self.memo().peek_unchecked() } } diff --git a/packages/signals/src/global/signal.rs b/packages/signals/src/global/signal.rs index 3dd57f484..f292f7444 100644 --- a/packages/signals/src/global/signal.rs +++ b/packages/signals/src/global/signal.rs @@ -1,6 +1,6 @@ use crate::write::Writable; -use crate::Write; use crate::{read::Readable, ReadableRef}; +use crate::{WritableRef, Write}; use dioxus_core::prelude::{IntoAttributeValue, ScopeId}; use generational_box::UnsyncStorage; use std::{mem::MaybeUninit, ops::Deref}; @@ -44,8 +44,8 @@ impl GlobalSignal { } /// Write this value - pub fn write(&self) -> Write { - self.signal().write() + pub fn write(&self) -> Write<'static, T, UnsyncStorage> { + self.signal().try_write_unchecked().unwrap() } /// Get the scope the signal was created in. @@ -71,23 +71,25 @@ impl Readable for GlobalSignal { type Storage = UnsyncStorage; #[track_caller] - fn try_read(&self) -> Result, generational_box::BorrowError> { - self.signal().try_read() + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + self.signal().try_read_unchecked() } #[track_caller] - fn peek(&self) -> ReadableRef { - self.signal().peek() + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { + self.signal().peek_unchecked() } } impl Writable for GlobalSignal { - type Mut = Write; + type Mut<'a, R: ?Sized + 'static> = Write<'a, R, UnsyncStorage>; fn map_mut &mut U>( - ref_: Self::Mut, + ref_: Self::Mut<'_, I>, f: F, - ) -> Self::Mut { + ) -> Self::Mut<'_, U> { Write::map(ref_, f) } @@ -96,15 +98,21 @@ impl Writable for GlobalSignal { U: ?Sized + 'static, F: FnOnce(&mut I) -> Option<&mut U>, >( - ref_: Self::Mut, + ref_: Self::Mut<'_, I>, f: F, - ) -> Option> { + ) -> Option> { Write::filter_map(ref_, f) } + fn downcast_mut<'a: 'b, 'b, R: ?Sized + 'static>(mut_: Self::Mut<'a, R>) -> Self::Mut<'b, R> { + Write::downcast_lifetime(mut_) + } + #[track_caller] - fn try_write(&mut self) -> Result, generational_box::BorrowMutError> { - self.signal().try_write() + fn try_write_unchecked( + &self, + ) -> Result, generational_box::BorrowMutError> { + self.signal().try_write_unchecked() } } diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index e203a6d0e..e3c548d6a 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -6,8 +6,8 @@ use generational_box::{AnyStorage, UnsyncStorage}; /// A read only signal that has been mapped to a new type. pub struct MappedSignal { - try_read: Rc Result, generational_box::BorrowError> + 'static>, - peek: Rc S::Ref + 'static>, + try_read: Rc Result, generational_box::BorrowError> + 'static>, + peek: Rc S::Ref<'static, O> + 'static>, } impl Clone for MappedSignal { @@ -26,8 +26,10 @@ where { /// Create a new mapped signal. pub(crate) fn new( - try_read: Rc Result, generational_box::BorrowError> + 'static>, - peek: Rc S::Ref + 'static>, + try_read: Rc< + dyn Fn() -> Result, generational_box::BorrowError> + 'static, + >, + peek: Rc S::Ref<'static, O> + 'static>, ) -> Self { MappedSignal { try_read, peek } } @@ -41,11 +43,13 @@ where type Target = O; type Storage = S; - fn try_read(&self) -> Result, generational_box::BorrowError> { + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { (self.try_read)() } - fn peek(&self) -> ReadableRef { + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { (self.peek)() } } diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index e5761eb58..86d6405e1 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -163,8 +163,10 @@ where type Storage = UnsyncStorage; #[track_caller] - fn try_read(&self) -> Result, generational_box::BorrowError> { - let read = self.inner.try_read(); + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + let read = self.inner.try_read_unchecked(); match read { Ok(r) => { let needs_update = self @@ -175,7 +177,7 @@ where if needs_update { drop(r); self.recompute(); - self.inner.try_read() + self.inner.try_read_unchecked() } else { Ok(r) } @@ -188,8 +190,8 @@ where /// /// If the signal has been dropped, this will panic. #[track_caller] - fn peek(&self) -> ReadableRef { - self.inner.peek() + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { + self.inner.peek_unchecked() } } diff --git a/packages/signals/src/reactive_context.rs b/packages/signals/src/reactive_context.rs index a1b08db24..ec2554f81 100644 --- a/packages/signals/src/reactive_context.rs +++ b/packages/signals/src/reactive_context.rs @@ -128,8 +128,7 @@ impl ReactiveContext { /// /// Returns true if the context was marked as dirty, or false if the context has been dropped pub fn mark_dirty(&self) -> bool { - let mut copy = self.inner; - if let Ok(mut self_write) = copy.try_write() { + if let Ok(mut self_write) = self.inner.try_write_unchecked() { #[cfg(debug_assertions)] { tracing::trace!( diff --git a/packages/signals/src/read.rs b/packages/signals/src/read.rs index 7effa39f7..3bc953cdb 100644 --- a/packages/signals/src/read.rs +++ b/packages/signals/src/read.rs @@ -6,7 +6,8 @@ use crate::MappedSignal; /// A reference to a value that can be read from. #[allow(type_alias_bounds)] -pub type ReadableRef::Target> = ::Ref; +pub type ReadableRef<'a, T: Readable, O = ::Target> = + ::Ref<'a, O>; /// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadOnlySignal`]. 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. pub trait Readable { @@ -27,15 +28,17 @@ pub trait Readable { let mapping = mapping.clone(); move || { self_ - .try_read() + .try_read_unchecked() .map(|ref_| ::map(ref_, |r| mapping(r))) } }) as Rc< - dyn Fn() -> Result, generational_box::BorrowError> + 'static, + dyn Fn() -> Result, generational_box::BorrowError> + + 'static, >; - let peek = Rc::new(move || ::map(self.peek(), |r| mapping(r))) - as Rc ReadableRef + 'static>; + let peek = Rc::new(move || { + ::map(self.peek_unchecked(), |r| mapping(r)) + }) as Rc ReadableRef<'static, Self, O> + 'static>; MappedSignal::new(try_read, peek) } @@ -47,12 +50,38 @@ pub trait Readable { self.try_read().unwrap() } - /// Try to get the current value of the state. If this is a signal, this will subscribe the current scope to the signal. If the value has been dropped, this will panic. + /// Try to get the current value of the state. If this is a signal, this will subscribe the current scope to the signal. #[track_caller] - fn try_read(&self) -> Result, generational_box::BorrowError>; + fn try_read(&self) -> Result, generational_box::BorrowError> { + self.try_read_unchecked().map(Self::Storage::downcast_ref) + } + + /// Try to get a reference to the value without checking the lifetime. + /// + /// NOTE: This method is completely safe because borrow checking is done at runtime. + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError>; + + /// Tet a reference to the value without checking the lifetime. + /// + /// NOTE: This method is completely safe because borrow checking is done at runtime. + fn read_unchecked(&self) -> ReadableRef<'static, Self> { + self.try_read_unchecked().unwrap() + } + + /// 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.** + /// + /// If the signal has been dropped, this will panic. + /// + /// NOTE: This method is completely safe because borrow checking is done at runtime. + fn peek_unchecked(&self) -> ReadableRef<'static, Self>; /// Get the current value of the state without subscribing to updates. If the value has been dropped, this will panic. - fn peek(&self) -> ReadableRef; + #[track_caller] + fn peek(&self) -> ReadableRef { + Self::Storage::downcast_ref(self.peek_unchecked()) + } /// Clone the inner value and return it. If the value has been dropped, this will panic. #[track_caller] @@ -171,7 +200,7 @@ pub struct ReadableValueIterator<'a, R> { } impl<'a, T: 'static, R: Readable>> Iterator for ReadableValueIterator<'a, R> { - type Item = ReadableRef; + type Item = ReadableRef<'a, R, T>; fn next(&mut self) -> Option { let index = self.index; diff --git a/packages/signals/src/read_only_signal.rs b/packages/signals/src/read_only_signal.rs index 54106be31..26e7730c6 100644 --- a/packages/signals/src/read_only_signal.rs +++ b/packages/signals/src/read_only_signal.rs @@ -59,16 +59,18 @@ impl>> Readable for ReadOnlySignal { type Storage = S; #[track_caller] - fn try_read(&self) -> Result, generational_box::BorrowError> { - self.inner.try_read() + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + self.inner.try_read_unchecked() } /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. #[track_caller] - fn peek(&self) -> S::Ref { - self.inner.peek() + fn peek_unchecked(&self) -> S::Ref<'static, T> { + self.inner.peek_unchecked() } } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 80fb7953d..aa65f60fd 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,8 +1,8 @@ -use crate::Memo; use crate::{ read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, ReactiveContext, ReadableRef, }; +use crate::{Memo, WritableRef}; use dioxus_core::{prelude::IntoAttributeValue, ScopeId}; use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage}; use std::{ @@ -168,8 +168,10 @@ impl>> Readable for Signal { type Storage = S; #[track_caller] - fn try_read(&self) -> Result, generational_box::BorrowError> { - let inner = self.inner.try_read()?; + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + let inner = self.inner.try_read_unchecked()?; if let Some(reactive_context) = ReactiveContext::current() { tracing::trace!("Subscribing to the reactive context {}", reactive_context); @@ -182,19 +184,20 @@ impl>> Readable for Signal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - fn peek(&self) -> ReadableRef { - let inner = self.inner.read(); + #[track_caller] + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { + let inner = self.inner.try_read_unchecked().unwrap(); S::map(inner, |v| &v.value) } } impl>> Writable for Signal { - type Mut = Write; + type Mut<'a, R: ?Sized + 'static> = Write<'a, R, S>; fn map_mut &mut U>( - ref_: Self::Mut, + ref_: Self::Mut<'_, I>, f: F, - ) -> Self::Mut { + ) -> Self::Mut<'_, U> { Write::map(ref_, f) } @@ -203,15 +206,21 @@ impl>> Writable for Signal { U: ?Sized + 'static, F: FnOnce(&mut I) -> Option<&mut U>, >( - ref_: Self::Mut, + ref_: Self::Mut<'_, I>, f: F, - ) -> Option> { + ) -> Option> { Write::filter_map(ref_, f) } + fn downcast_mut<'a: 'b, 'b, R: ?Sized + 'static>(mut_: Self::Mut<'a, R>) -> Self::Mut<'b, R> { + Write::downcast_lifetime(mut_) + } + #[track_caller] - fn try_write(&mut self) -> Result, generational_box::BorrowMutError> { - self.inner.try_write().map(|inner| { + fn try_write_unchecked( + &self, + ) -> Result, generational_box::BorrowMutError> { + self.inner.try_write_unchecked().map(|inner| { let borrow = S::map_mut(inner, |v| &mut v.value); Write { write: borrow, @@ -273,14 +282,14 @@ impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage>> /// /// T is the current type of the write /// S is the storage type of the signal -pub struct Write { - write: S::Mut, +pub struct Write<'a, T: ?Sized + 'static, S: AnyStorage = UnsyncStorage> { + write: S::Mut<'a, T>, drop_signal: Box, } -impl Write { +impl<'a, T: ?Sized + 'static, S: AnyStorage> Write<'a, T, S> { /// Map the mutable reference to the signal's value to a new type. - pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write { + pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, S> { let Self { write, drop_signal, .. } = myself; @@ -294,16 +303,27 @@ impl Write { pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, - ) -> Option> { + ) -> Option> { let Self { write, drop_signal, .. } = myself; let write = S::try_map_mut(write, f); write.map(|write| Write { write, drop_signal }) } + + /// Downcast the lifetime of the mutable reference to the signal's value. + pub fn downcast_lifetime<'b>(mut_: Self) -> Write<'b, T, S> + where + 'a: 'b, + { + Write { + write: S::downcast_mut(mut_.write), + drop_signal: mut_.drop_signal, + } + } } -impl Deref for Write { +impl Deref for Write<'_, T, S> { type Target = T; fn deref(&self) -> &Self::Target { @@ -311,7 +331,7 @@ impl Deref for Write { } } -impl DerefMut for Write { +impl DerefMut for Write<'_, T, S> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } diff --git a/packages/signals/src/write.rs b/packages/signals/src/write.rs index 8abc54b44..c4b8582e5 100644 --- a/packages/signals/src/write.rs +++ b/packages/signals/src/write.rs @@ -1,33 +1,58 @@ -use std::ops::DerefMut; -use std::ops::IndexMut; +use std::ops::{DerefMut, IndexMut}; use crate::read::Readable; +/// A reference to a value that can be read from. +#[allow(type_alias_bounds)] +pub type WritableRef<'a, T: Writable, O = ::Target> = T::Mut<'a, O>; + /// A trait for states that can be read from like [`crate::Signal`], or [`crate::GlobalSignal`]. 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 [`Writable`] type. pub trait Writable: Readable { /// The type of the reference. - type Mut: DerefMut + 'static; + type Mut<'a, R: ?Sized + 'static>: DerefMut; /// Map the reference to a new type. fn map_mut &mut U>( - ref_: Self::Mut, + ref_: Self::Mut<'_, I>, f: F, - ) -> Self::Mut; + ) -> Self::Mut<'_, U>; /// Try to map the reference to a new type. fn try_map_mut Option<&mut U>>( - ref_: Self::Mut, + ref_: Self::Mut<'_, I>, f: F, - ) -> Option>; + ) -> Option>; + + /// Downcast a mutable reference in a RefMut to a more specific lifetime + /// + /// This function enforces the variance of the lifetime parameter `'a` in Ref. + fn downcast_mut<'a: 'b, 'b, T: ?Sized + 'static>(mut_: Self::Mut<'a, T>) -> Self::Mut<'b, T>; /// Get a mutable reference to the value. If the value has been dropped, this will panic. #[track_caller] - fn write(&mut self) -> Self::Mut { + fn write(&mut self) -> WritableRef<'_, Self> { self.try_write().unwrap() } - /// Try to get a mutable reference to the value. If the value has been dropped, this will panic. - fn try_write(&mut self) -> Result, generational_box::BorrowMutError>; + /// Try to get a mutable reference to the value. + #[track_caller] + fn try_write(&mut self) -> Result, generational_box::BorrowMutError> { + self.try_write_unchecked().map(Self::downcast_mut) + } + + /// Try to get a mutable reference to the value without checking the lifetime. + /// + /// NOTE: This method is completely safe because borrow checking is done at runtime. + fn try_write_unchecked( + &self, + ) -> Result, generational_box::BorrowMutError>; + + /// Tet a mutable reference to the value without checking the lifetime. + /// + /// NOTE: This method is completely safe because borrow checking is done at runtime. + fn write_unchecked(&self) -> WritableRef<'static, Self> { + self.try_write_unchecked().unwrap() + } /// Run a function with a mutable reference to the value. If the value has been dropped, this will panic. #[track_caller] @@ -55,7 +80,10 @@ pub trait Writable: Readable { /// Index into the inner value and return a reference to the result. #[track_caller] - fn index_mut(&mut self, index: I) -> Self::Mut<>::Output> + fn index_mut( + &mut self, + index: I, + ) -> WritableRef<'_, Self, >::Output> where Self::Target: std::ops::IndexMut, { @@ -84,15 +112,14 @@ pub trait Writable: Readable { /// An extension trait for Writable> that provides some convenience methods. pub trait WritableOptionExt: Writable> { /// Gets the value out of the Option, or inserts the given value if the Option is empty. - fn get_or_insert(&mut self, default: T) -> Self::Mut { + fn get_or_insert(&mut self, default: T) -> WritableRef<'_, Self, T> { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. - fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> Self::Mut { - let borrow = self.read(); - if borrow.is_none() { - drop(borrow); + fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> WritableRef<'_, Self, T> { + let is_none = self.read().is_none(); + if is_none { self.with_mut(|v| *v = Some(default())); Self::map_mut(self.write(), |v| v.as_mut().unwrap()) } else { @@ -102,7 +129,7 @@ pub trait WritableOptionExt: Writable> { /// Attempts to write the inner value of the Option. #[track_caller] - fn as_mut(&mut self) -> Option> { + fn as_mut(&mut self) -> Option> { Self::try_map_mut(self.write(), |v: &mut Option| v.as_mut()) } } @@ -178,36 +205,40 @@ pub trait WritableVecExt: Writable> { /// Try to mutably get an element from the vector. #[track_caller] - fn get_mut(&mut self, index: usize) -> Option> { + fn get_mut(&mut self, index: usize) -> Option> { Self::try_map_mut(self.write(), |v: &mut Vec| v.get_mut(index)) } /// Gets an iterator over the values of the vector. #[track_caller] - fn iter_mut(&self) -> WritableValueIterator + fn iter_mut(&mut self) -> WritableValueIterator<'_, Self> where Self: Sized + Clone, { WritableValueIterator { index: 0, - value: self.clone(), + value: self, } } } /// An iterator over the values of a `Writable>`. -pub struct WritableValueIterator { +pub struct WritableValueIterator<'a, R> { index: usize, - value: R, + value: &'a mut R, } -impl>> Iterator for WritableValueIterator { - type Item = R::Mut; +impl<'a, T: 'static, R: Writable>> Iterator for WritableValueIterator<'a, R> { + type Item = WritableRef<'a, R, T>; fn next(&mut self) -> Option { let index = self.index; self.index += 1; - self.value.get_mut(index) + R::try_map_mut( + self.value.try_write_unchecked().unwrap(), + |v: &mut Vec| v.get_mut(index), + ) + .map(R::downcast_mut) } }