diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 5e20e30c25..9f3b1da5d3 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 05c651deaf..7a26dd5b68 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/desktop/headless_tests/rendering.rs b/packages/desktop/headless_tests/rendering.rs index df050ed103..f5e63acd85 100644 --- a/packages/desktop/headless_tests/rendering.rs +++ b/packages/desktop/headless_tests/rendering.rs @@ -25,14 +25,13 @@ fn use_inner_html(id: &'static str) -> Option { .unwrap(); if let Some(html) = res.as_str() { - // serde_json::Value::String(html) println!("html: {}", html); value.set(Some(html.to_string())); } }); }); - value.read().clone() + value() } const EXPECTED_HTML: &str = r#"

text

hello world

"#; diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 341ef89bf6..95e5ef056f 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() } @@ -186,8 +186,8 @@ impl> GenerationalBox { } } - /// Take the value out of the generational box and invalidate the generational box. This will return the value if the value was taken. - pub fn take(&self) -> Option { + /// Drop the value out of the generational box and invalidate the generational box. This will return the value if the value was taken. + pub fn manually_drop(&self) -> Option { if self.validate() { Storage::take(&self.raw.0.data) } else { @@ -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,49 @@ 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. Rust will typically infer this cast with a concrete type, but it cannot with a generic type. + fn downcast_lifetime_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 Mut. Rust will typically infer this cast with a concrete type, but it cannot with a generic type. + fn downcast_lifetime_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 1edd9a12ea..28730d7afc 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -20,13 +20,25 @@ 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_lifetime_ref<'a: 'b, 'b, T: ?Sized + 'static>( + ref_: Self::Ref<'a, T>, + ) -> Self::Ref<'b, T> { + ref_ + } + + fn downcast_lifetime_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 +58,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 +113,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 +144,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 da08cf4c43..ed0f791f3c 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,25 @@ 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_lifetime_ref<'a: 'b, 'b, T: ?Sized + 'static>( + ref_: Self::Ref<'a, T>, + ) -> Self::Ref<'b, T> { + ref_ + } + + fn downcast_lifetime_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 +121,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/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index 64ac0c2ff4..904b7d8e02 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -123,7 +123,7 @@ fn Route3(dynamic: String) -> Element { oninput: move |evt| { *current_route_str.write() = evt.value(); }, - value: "{current_route_str.read()}" + value: "{current_route_str}" } "dynamic: {dynamic}" Link { to: Route::Route2 { user_id: 8888 }, "hello world link" } diff --git a/packages/router/src/contexts/router.rs b/packages/router/src/contexts/router.rs index f4c8bcaea5..85f5ac9eda 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/examples/errors.rs b/packages/signals/examples/errors.rs index 31b89f43a4..9f1797c9df 100644 --- a/packages/signals/examples/errors.rs +++ b/packages/signals/examples/errors.rs @@ -30,9 +30,9 @@ fn app() -> Element { #[component] fn Read() -> Element { - let mut signal = use_signal_sync(|| 0); + let signal = use_signal_sync(|| 0); - let _write = signal.write(); + let _write = signal.write_unchecked(); let _read = signal.read(); unreachable!() @@ -40,10 +40,10 @@ fn Read() -> Element { #[component] fn ReadMut() -> Element { - let mut signal = use_signal_sync(|| 0); + let signal = use_signal_sync(|| 0); let _read = signal.read(); - let _write = signal.write(); + let _write = signal.write_unchecked(); unreachable!() } diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index cb37d980f9..97ee1ffbc9 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. @@ -189,11 +190,9 @@ impl> CopyValue { } } - /// Take the value out of the CopyValue, invalidating the value in the process. - pub fn take(&self) -> T { - self.value - .take() - .expect("value is already dropped or borrowed") + /// Manually drop the value in the CopyValue, invalidating the value in the process. + pub fn manually_drop(&self) -> Option { + self.value.manually_drop() } /// Get the scope this value was created in. @@ -211,40 +210,45 @@ 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_lifetime_mut<'a: 'b, 'b, R: ?Sized + 'static>( + mut_: Self::Mut<'a, R>, + ) -> Self::Mut<'b, R> { + S::downcast_lifetime_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 61d1c10f40..67e9544fc3 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 3dd57f4844..7bdab6e0b5 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,23 @@ 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_lifetime_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 e203a6d0e7..e3c548d6a0 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 e5761eb581..86d6405e18 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 a1b08db240..ec2554f811 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 7effa39f71..0a06ec268b 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,39 @@ 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_lifetime_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_lifetime_ref(self.peek_unchecked()) + } /// Clone the inner value and return it. If the value has been dropped, this will panic. #[track_caller] @@ -171,7 +201,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 54106be310..618049eb14 100644 --- a/packages/signals/src/read_only_signal.rs +++ b/packages/signals/src/read_only_signal.rs @@ -50,7 +50,9 @@ impl>> ReadOnlySignal { #[doc(hidden)] /// This should only be used by the `rsx!` macro. pub fn __take(&self) -> T { - self.inner.take() + self.inner + .manually_drop() + .expect("Signal has already been dropped") } } @@ -59,16 +61,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 80fb7953d1..1c530af296 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::{ @@ -136,9 +136,9 @@ impl>> Signal { } } - /// Take the value out of the signal, invalidating the signal in the process. - pub fn take(&self) -> T { - self.inner.take().value + /// Drop the value out of the signal, invalidating the signal in the process. + pub fn manually_drop(&self) -> Option { + self.inner.manually_drop().map(|i| i.value) } /// Get the scope the signal was created in. @@ -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,23 @@ 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_lifetime_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 +284,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 +305,29 @@ 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. + /// + /// This function enforces the variance of the lifetime parameter `'a` in Mut. Rust will typically infer this cast with a concrete type, but it cannot with a generic type. + pub fn downcast_lifetime<'b>(mut_: Self) -> Write<'b, T, S> + where + 'a: 'b, + { + Write { + write: S::downcast_lifetime_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 +335,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 8abc54b444..2181da3dc5 100644 --- a/packages/signals/src/write.rs +++ b/packages/signals/src/write.rs @@ -1,33 +1,60 @@ -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_lifetime_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_lifetime_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 +82,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 +114,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 +131,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 +207,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_lifetime_mut) } }