From 5b44379af25b2740af2f979d26f57ed9d5dbf99a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 25 Aug 2025 09:55:19 -0500 Subject: [PATCH 1/4] Add some traits read only signal works with sync storage --- packages/core-macro/src/props/mod.rs | 1 + packages/signals/examples/send.rs | 14 +- packages/signals/examples/send_store.rs | 27 +++ packages/signals/src/boxed.rs | 257 ++++++++++++++++++------ packages/signals/src/write.rs | 2 +- packages/stores/src/store.rs | 74 +++++-- 6 files changed, 292 insertions(+), 83 deletions(-) create mode 100644 packages/signals/examples/send_store.rs diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index d994b9057f..47356ecd7e 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -1766,6 +1766,7 @@ fn looks_like_write_type(ty: &Type) -> bool { fn looks_like_store_type(ty: &Type) -> bool { last_segment_matches(ty, &parse_quote!(Store)) || last_segment_matches(ty, &parse_quote!(ReadStore)) + || last_segment_matches(ty, &parse_quote!(WriteStore)) } fn looks_like_callback_type(ty: &Type) -> bool { diff --git a/packages/signals/examples/send.rs b/packages/signals/examples/send.rs index 3591d9f058..b55d2952f9 100644 --- a/packages/signals/examples/send.rs +++ b/packages/signals/examples/send.rs @@ -7,6 +7,15 @@ fn main() { fn app() -> Element { let mut signal = use_signal_sync(|| 0); + rsx! { + button { onclick: move |_| signal += 1, "Increase" } + "{signal}" + Child { signal } + } +} + +#[component] +fn Child(signal: WriteSignal) -> Element { use_hook(|| { std::thread::spawn(move || loop { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -14,8 +23,5 @@ fn app() -> Element { }); }); - rsx! { - button { onclick: move |_| signal += 1, "Increase" } - "{signal}" - } + rsx! {} } diff --git a/packages/signals/examples/send_store.rs b/packages/signals/examples/send_store.rs new file mode 100644 index 0000000000..60768c6c21 --- /dev/null +++ b/packages/signals/examples/send_store.rs @@ -0,0 +1,27 @@ +use dioxus::prelude::*; + +fn main() { + dioxus::launch(app); +} + +fn app() -> Element { + let mut store = use_hook(|| Store::new_maybe_sync(0u32)); + + rsx! { + button { onclick: move |_| store += 1, "Increase" } + "{store}" + Child { store } + } +} + +#[component] +fn Child(store: WriteStore) -> Element { + use_hook(|| { + std::thread::spawn(move || loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + store += 1; + }); + }); + + rsx! {} +} diff --git a/packages/signals/src/boxed.rs b/packages/signals/src/boxed.rs index 6f97679539..b1d3c9ac3a 100644 --- a/packages/signals/src/boxed.rs +++ b/packages/signals/src/boxed.rs @@ -1,11 +1,12 @@ use std::{any::Any, ops::Deref}; use dioxus_core::{IntoAttributeValue, IntoDynNode, Subscribers}; -use generational_box::{BorrowResult, UnsyncStorage}; +use generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage}; use crate::{ read_impls, write_impls, CopyValue, Global, InitializeFromFunction, MappedMutSignal, - MappedSignal, Memo, Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt, + MappedSignal, Memo, Readable, ReadableExt, ReadableRef, Signal, SignalData, Writable, + WritableExt, }; /// A signal that can only be read from. @@ -13,18 +14,116 @@ use crate::{ since = "0.7.0", note = "Use `ReadSignal` instead. Will be removed in 0.8" )] -pub type ReadOnlySignal = ReadSignal; +pub type ReadOnlySignal = ReadSignal; + +mod sealed { + use generational_box::{SyncStorage, UnsyncStorage}; + + pub trait Sealed {} + impl Sealed for UnsyncStorage {} + impl Sealed for SyncStorage {} + + pub struct SealedToken; +} + +/// A trait for creating boxed readable and writable signals. This is implemented for +/// [UnsyncStorage] and [SyncStorage]. +pub trait ReadOnlySignalStorage: + Storage> + Storage> + sealed::Sealed + 'static +{ + #[doc(hidden)] + type DynReadable: Readable + ?Sized; + #[doc(hidden)] + type DynWritable: Writable> + ?Sized; +} + +/// A trait for creating boxed readable and writable signals. This is implemented for +/// [UnsyncStorage] and [SyncStorage]. +/// +/// The storage type must implement `CreateReadOnlySignalStorage` for the specific readable type +/// to be used with `ReadSignal` and `WriteSignal`. +pub trait CreateReadOnlySignalStorage: + ReadOnlySignalStorage + 'static +{ + #[doc(hidden)] + fn new_readable(value: T, _: sealed::SealedToken) -> Box + where + T: Sized; + + #[doc(hidden)] + fn new_writable(value: T, _: sealed::SealedToken) -> Box + where + T: Writable + Sized; +} + +impl ReadOnlySignalStorage for UnsyncStorage { + type DynReadable = dyn Readable; + type DynWritable = dyn Writable>; +} + +impl + ?Sized + 'static> CreateReadOnlySignalStorage + for UnsyncStorage +{ + fn new_readable(value: T, _: sealed::SealedToken) -> Box + where + T: Sized, + { + Box::new(value) + } + + fn new_writable(value: T, _: sealed::SealedToken) -> Box + where + T: Writable + Sized, + { + Box::new(BoxWriteMetadata::new(value)) + } +} + +impl ReadOnlySignalStorage for SyncStorage { + type DynReadable = dyn Readable + Send + Sync; + type DynWritable = + dyn Writable> + Send + Sync; +} + +impl + Sync + Send + ?Sized + 'static> + CreateReadOnlySignalStorage for SyncStorage +{ + fn new_readable(value: T, _: sealed::SealedToken) -> Box + where + T: Sized, + { + Box::new(value) + } + + fn new_writable(value: T, _: sealed::SealedToken) -> Box + where + T: Writable + Sized, + { + Box::new(BoxWriteMetadata::new(value)) + } +} /// A boxed version of [Readable] that can be used to store any readable type. -pub struct ReadSignal { - value: CopyValue>>, +pub struct ReadSignal = UnsyncStorage> { + value: CopyValue, S>, } impl ReadSignal { /// Create a new boxed readable value. pub fn new(value: impl Readable + 'static) -> Self { + Self::new_maybe_sync(value) + } +} + +impl> ReadSignal { + /// Create a new boxed readable value which may be sync + pub fn new_maybe_sync(value: R) -> Self + where + S: CreateReadOnlySignalStorage, + R: Readable, + { Self { - value: CopyValue::new(Box::new(value)), + value: CopyValue::new_maybe_sync(S::new_readable(value, sealed::SealedToken)), } } @@ -56,29 +155,35 @@ impl ReadSignal { } } -impl Clone for ReadSignal { +impl> Clone for ReadSignal { fn clone(&self) -> Self { *self } } -impl Copy for ReadSignal {} +impl> Copy for ReadSignal {} -impl PartialEq for ReadSignal { +impl> PartialEq for ReadSignal { fn eq(&self, other: &Self) -> bool { self.value == other.value } } -impl Default for ReadSignal { +impl< + T: Default + 'static, + S: CreateReadOnlySignalStorage> + + ReadOnlySignalStorage + + Storage>, + > Default for ReadSignal +{ fn default() -> Self { - Self::new(Signal::new(T::default())) + Self::new_maybe_sync(Signal::new_maybe_sync(T::default())) } } -read_impls!(ReadSignal); +read_impls!(ReadSignal>); -impl IntoAttributeValue for ReadSignal +impl> IntoAttributeValue for ReadSignal where T: Clone + IntoAttributeValue + 'static, { @@ -87,16 +192,17 @@ where } } -impl IntoDynNode for ReadSignal +impl IntoDynNode for ReadSignal where T: Clone + IntoDynNode + 'static, + S: ReadOnlySignalStorage, { fn into_dyn_node(self) -> dioxus_core::DynamicNode { self.with(|f| f.clone().into_dyn_node()) } } -impl Deref for ReadSignal { +impl> Deref for ReadSignal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { @@ -104,9 +210,9 @@ impl Deref for ReadSignal { } } -impl Readable for ReadSignal { +impl> Readable for ReadSignal { type Target = T; - type Storage = UnsyncStorage; + type Storage = S; #[track_caller] fn try_read_unchecked( @@ -143,9 +249,15 @@ impl Readable for ReadSignal { // We can't implement From > for ReadSignal // because it would conflict with the From for T implementation, but we can implement it for // all specific readable types -impl From> for ReadSignal { - fn from(value: Signal) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateReadOnlySignalStorage> + + ReadOnlySignalStorage + + Storage>, + > From> for ReadSignal +{ + fn from(value: Signal) -> Self { + Self::new_maybe_sync(value) } } impl From> for ReadSignal { @@ -153,9 +265,13 @@ impl From> for ReadSignal { Self::new(value) } } -impl From> for ReadSignal { - fn from(value: CopyValue) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage + Storage, + > From> for ReadSignal +{ + fn from(value: CopyValue) -> Self { + Self::new_maybe_sync(value) } } impl From> for ReadSignal @@ -167,38 +283,41 @@ where Self::new(value) } } -impl From> for ReadSignal +impl From> for ReadSignal where O: ?Sized + 'static, - V: Readable + 'static, + V: Readable + 'static, F: Fn(&V::Target) -> &O + 'static, + S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, { fn from(value: MappedSignal) -> Self { - Self::new(value) + Self::new_maybe_sync(value) } } -impl From> for ReadSignal +impl From> for ReadSignal where O: ?Sized + 'static, - V: Readable + 'static, + V: Readable + 'static, F: Fn(&V::Target) -> &O + 'static, FMut: 'static, + S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, { fn from(value: MappedMutSignal) -> Self { - Self::new(value) + Self::new_maybe_sync(value) } } -impl From> for ReadSignal { - fn from(value: WriteSignal) -> Self { - Self::new(value) +impl From> for ReadSignal +where + S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, +{ + fn from(value: WriteSignal) -> Self { + Self::new_maybe_sync(value) } } /// A boxed version of [Writable] that can be used to store any writable type. -pub struct WriteSignal { - value: CopyValue< - Box>>, - >, +pub struct WriteSignal = UnsyncStorage> { + value: CopyValue, S>, } impl WriteSignal { @@ -206,8 +325,19 @@ impl WriteSignal { pub fn new( value: impl Writable + 'static, ) -> Self { + Self::new_maybe_sync(value) + } +} + +impl> WriteSignal { + /// Create a new boxed writable value which may be sync + pub fn new_maybe_sync(value: R) -> Self + where + R: Writable, + S: CreateReadOnlySignalStorage, + { Self { - value: CopyValue::new(Box::new(BoxWriteMetadata::new(value))), + value: CopyValue::new_maybe_sync(S::new_writable(value, sealed::SealedToken)), } } } @@ -272,42 +402,44 @@ where } } -impl Clone for WriteSignal { +impl> Clone for WriteSignal { fn clone(&self) -> Self { *self } } -impl Copy for WriteSignal {} +impl> Copy for WriteSignal {} -impl PartialEq for WriteSignal { +impl> PartialEq for WriteSignal { fn eq(&self, other: &Self) -> bool { self.value == other.value } } -read_impls!(WriteSignal); -write_impls!(WriteSignal); +read_impls!(WriteSignal>); +write_impls!(WriteSignal>); -impl IntoAttributeValue for WriteSignal +impl IntoAttributeValue for WriteSignal where T: Clone + IntoAttributeValue + 'static, + S: ReadOnlySignalStorage, { fn into_value(self) -> dioxus_core::AttributeValue { self.with(|f| f.clone().into_value()) } } -impl IntoDynNode for WriteSignal +impl IntoDynNode for WriteSignal where T: Clone + IntoDynNode + 'static, + S: ReadOnlySignalStorage, { fn into_dyn_node(self) -> dioxus_core::DynamicNode { self.with(|f| f.clone().into_dyn_node()) } } -impl Deref for WriteSignal { +impl> Deref for WriteSignal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { @@ -315,9 +447,9 @@ impl Deref for WriteSignal { } } -impl Readable for WriteSignal { +impl> Readable for WriteSignal { type Target = T; - type Storage = UnsyncStorage; + type Storage = S; #[track_caller] fn try_read_unchecked( @@ -351,7 +483,7 @@ impl Readable for WriteSignal { } } -impl Writable for WriteSignal { +impl> Writable for WriteSignal { type WriteMetadata = Box; fn try_write_unchecked( @@ -370,14 +502,24 @@ impl Writable for WriteSignal { // We can't implement From> for Write // because it would conflict with the From for T implementation, but we can implement it for // all specific readable types -impl From> for WriteSignal { - fn from(value: Signal) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateReadOnlySignalStorage> + + ReadOnlySignalStorage + + Storage>, + > From> for WriteSignal +{ + fn from(value: Signal) -> Self { + Self::new_maybe_sync(value) } } -impl From> for WriteSignal { - fn from(value: CopyValue) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage + Storage, + > From> for WriteSignal +{ + fn from(value: CopyValue) -> Self { + Self::new_maybe_sync(value) } } impl From> for WriteSignal @@ -389,14 +531,15 @@ where Self::new(value) } } -impl From> for WriteSignal +impl From> for WriteSignal where O: ?Sized + 'static, - V: Writable + 'static, + V: Writable + 'static, F: Fn(&V::Target) -> &O + 'static, FMut: Fn(&mut V::Target) -> &mut O + 'static, + S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage, { fn from(value: MappedMutSignal) -> Self { - Self::new(value) + Self::new_maybe_sync(value) } } diff --git a/packages/signals/src/write.rs b/packages/signals/src/write.rs index 461ca6ef95..098ee6561d 100644 --- a/packages/signals/src/write.rs +++ b/packages/signals/src/write.rs @@ -59,7 +59,7 @@ pub trait Writable: Readable { /// # use dioxus::prelude::*; /// fn app() -> Element { /// let mut value = use_signal(|| String::from("hello")); -/// +/// /// rsx! { /// button { /// onclick: move |_| { diff --git a/packages/stores/src/store.rs b/packages/stores/src/store.rs index e731d8a4b3..fe56fbdf81 100644 --- a/packages/stores/src/store.rs +++ b/packages/stores/src/store.rs @@ -6,9 +6,10 @@ use dioxus_core::{ use_hook, AttributeValue, DynamicNode, IntoAttributeValue, IntoDynNode, Subscribers, SuperInto, }; use dioxus_signals::{ - read_impls, write_impls, BorrowError, BorrowMutError, CopyValue, Global, - InitializeFromFunction, MappedMutSignal, ReadSignal, Readable, ReadableExt, ReadableRef, - Storage, UnsyncStorage, Writable, WritableExt, WritableRef, WriteSignal, + read_impls, write_impls, BorrowError, BorrowMutError, CopyValue, CreateReadOnlySignalStorage, + Global, InitializeFromFunction, MappedMutSignal, ReadOnlySignalStorage, ReadSignal, Readable, + ReadableExt, ReadableRef, Storage, UnsyncStorage, Writable, WritableExt, WritableRef, + WriteSignal, }; use std::marker::PhantomData; @@ -21,10 +22,10 @@ pub(crate) type MappedStore< > = Store>; /// A type alias for a boxed read-only store. -pub type ReadStore = Store>; +pub type ReadStore = Store>; /// A type alias for a boxed writable-only store. -pub type WriteStore = Store>; +pub type WriteStore = Store>; /// Stores are a reactive type built for nested data structures. Each store will lazily create signals /// for each field/member of the data structure as needed. @@ -181,12 +182,14 @@ where } impl Copy for Store where Lens: Copy {} -impl<__F, __FMut, T: ?Sized, Lens> ::std::convert::From> - for Store> +impl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From> + for WriteStore where - Lens: Writable + 'static, + Lens: Writable + 'static, __F: Fn(&Lens::Target) -> &T + 'static, __FMut: Fn(&mut Lens::Target) -> &mut T + 'static, + S: ReadOnlySignalStorage + + CreateReadOnlySignalStorage>, T: 'static, { fn from(value: MappedStore) -> Self { @@ -196,12 +199,14 @@ where } } } -impl<__F, __FMut, T: ?Sized, Lens> ::std::convert::From> - for Store> +impl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From> + for ReadStore where - Lens: Writable + 'static, + Lens: Writable + 'static, __F: Fn(&Lens::Target) -> &T + 'static, __FMut: Fn(&mut Lens::Target) -> &mut T + 'static, + S: ReadOnlySignalStorage + + CreateReadOnlySignalStorage>, T: 'static, { fn from(value: MappedStore) -> Self { @@ -211,11 +216,36 @@ where } } } -impl ::std::convert::From> for ReadStore +impl ::std::convert::From> for ReadStore where T: ?Sized + 'static, + S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, { - fn from(value: Store) -> Self { + fn from(value: Store>) -> Self { + Self { + selector: value.selector.map_writer(::std::convert::Into::into), + _phantom: ::std::marker::PhantomData, + } + } +} +impl ::std::convert::From>> for ReadStore +where + T: 'static, + S: ReadOnlySignalStorage + CreateReadOnlySignalStorage> + Storage, +{ + fn from(value: Store>) -> Self { + Self { + selector: value.selector.map_writer(::std::convert::Into::into), + _phantom: ::std::marker::PhantomData, + } + } +} +impl ::std::convert::From>> for WriteStore +where + T: 'static, + S: ReadOnlySignalStorage + CreateReadOnlySignalStorage> + Storage, +{ + fn from(value: Store>) -> Self { Self { selector: value.selector.map_writer(::std::convert::Into::into), _phantom: ::std::marker::PhantomData, @@ -225,25 +255,27 @@ where #[doc(hidden)] pub struct SuperIntoReadSignalMarker; -impl SuperInto, SuperIntoReadSignalMarker> for Store +impl SuperInto, SuperIntoReadSignalMarker> for Store where T: ?Sized + 'static, - Lens: Readable + 'static, + Lens: Readable + 'static, + S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage, { - fn super_into(self) -> ReadSignal { - ReadSignal::new(self) + fn super_into(self) -> ReadSignal { + ReadSignal::new_maybe_sync(self) } } #[doc(hidden)] pub struct SuperIntoWriteSignalMarker; -impl SuperInto, SuperIntoWriteSignalMarker> for Store +impl SuperInto, SuperIntoWriteSignalMarker> for Store where T: ?Sized + 'static, - Lens: Writable + 'static, + Lens: Writable + 'static, + S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage, { - fn super_into(self) -> WriteSignal { - WriteSignal::new(self) + fn super_into(self) -> WriteSignal { + WriteSignal::new_maybe_sync(self) } } From 922179976dba5bbf8b7afb995d15fb8d6e688456 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 25 Aug 2025 11:42:45 -0500 Subject: [PATCH 2/4] seal gats --- packages/signals/src/boxed.rs | 143 +++++++++++++++++++--------------- packages/stores/src/store.rs | 22 +++--- 2 files changed, 91 insertions(+), 74 deletions(-) diff --git a/packages/signals/src/boxed.rs b/packages/signals/src/boxed.rs index b1d3c9ac3a..f5a6a9bf03 100644 --- a/packages/signals/src/boxed.rs +++ b/packages/signals/src/boxed.rs @@ -24,54 +24,78 @@ mod sealed { impl Sealed for SyncStorage {} pub struct SealedToken; + + pub trait SealedTokenTrait {} + impl SealedTokenTrait for SealedToken {} } /// A trait for creating boxed readable and writable signals. This is implemented for /// [UnsyncStorage] and [SyncStorage]. -pub trait ReadOnlySignalStorage: - Storage> + Storage> + sealed::Sealed + 'static +/// +/// You may need to add this trait as a bound when you use [ReadSignal] or [WriteSignal] while +/// remaining generic over syncness. +pub trait BoxedSignalStorage: + Storage>> + + Storage>> + + sealed::Sealed + + 'static { + // This is not a public api, and is sealed to prevent external usage and implementations #[doc(hidden)] - type DynReadable: Readable + ?Sized; + type DynReadable: Readable + ?Sized; + // This is not a public api, and is sealed to prevent external usage and implementations #[doc(hidden)] - type DynWritable: Writable> + ?Sized; + type DynWritable: Writable> + + ?Sized; } /// A trait for creating boxed readable and writable signals. This is implemented for /// [UnsyncStorage] and [SyncStorage]. /// -/// The storage type must implement `CreateReadOnlySignalStorage` for the specific readable type +/// The storage type must implement `CreateReadOnlySignalStorage` for every readable `T` type /// to be used with `ReadSignal` and `WriteSignal`. -pub trait CreateReadOnlySignalStorage: - ReadOnlySignalStorage + 'static +/// +/// You may need to add this trait as a bound when you call [ReadSignal::new_maybe_sync] or +/// [WriteSignal::new_maybe_sync] while remaining generic over syncness. +pub trait CreateBoxedSignalStorage: + BoxedSignalStorage + 'static { + // This is not a public api, and is sealed to prevent external usage and implementations #[doc(hidden)] - fn new_readable(value: T, _: sealed::SealedToken) -> Box + fn new_readable( + value: T, + _: sealed::SealedToken, + ) -> Box> where T: Sized; + // This is not a public api, and is sealed to prevent external usage and implementations #[doc(hidden)] - fn new_writable(value: T, _: sealed::SealedToken) -> Box + fn new_writable( + value: T, + _: sealed::SealedToken, + ) -> Box> where T: Writable + Sized; } -impl ReadOnlySignalStorage for UnsyncStorage { - type DynReadable = dyn Readable; - type DynWritable = dyn Writable>; +impl BoxedSignalStorage for UnsyncStorage { + type DynReadable = dyn Readable; + type DynWritable = + dyn Writable>; } -impl + ?Sized + 'static> CreateReadOnlySignalStorage +impl + ?Sized + 'static> CreateBoxedSignalStorage for UnsyncStorage { - fn new_readable(value: T, _: sealed::SealedToken) -> Box + fn new_readable(value: T, _: sealed::SealedToken) -> Box> where T: Sized, { Box::new(value) } - fn new_writable(value: T, _: sealed::SealedToken) -> Box + fn new_writable(value: T, _: sealed::SealedToken) -> Box> where T: Writable + Sized, { @@ -79,23 +103,24 @@ impl + ?Sized + 'static> CreateReadOnlySign } } -impl ReadOnlySignalStorage for SyncStorage { - type DynReadable = dyn Readable + Send + Sync; - type DynWritable = +impl BoxedSignalStorage for SyncStorage { + type DynReadable = + dyn Readable + Send + Sync; + type DynWritable = dyn Writable> + Send + Sync; } impl + Sync + Send + ?Sized + 'static> - CreateReadOnlySignalStorage for SyncStorage + CreateBoxedSignalStorage for SyncStorage { - fn new_readable(value: T, _: sealed::SealedToken) -> Box + fn new_readable(value: T, _: sealed::SealedToken) -> Box> where T: Sized, { Box::new(value) } - fn new_writable(value: T, _: sealed::SealedToken) -> Box + fn new_writable(value: T, _: sealed::SealedToken) -> Box> where T: Writable + Sized, { @@ -104,8 +129,8 @@ impl + Sync + Send + ?Sized + 'static> } /// A boxed version of [Readable] that can be used to store any readable type. -pub struct ReadSignal = UnsyncStorage> { - value: CopyValue, S>, +pub struct ReadSignal = UnsyncStorage> { + value: CopyValue>, S>, } impl ReadSignal { @@ -115,11 +140,11 @@ impl ReadSignal { } } -impl> ReadSignal { +impl> ReadSignal { /// Create a new boxed readable value which may be sync pub fn new_maybe_sync(value: R) -> Self where - S: CreateReadOnlySignalStorage, + S: CreateBoxedSignalStorage, R: Readable, { Self { @@ -155,15 +180,15 @@ impl> ReadSignal { } } -impl> Clone for ReadSignal { +impl> Clone for ReadSignal { fn clone(&self) -> Self { *self } } -impl> Copy for ReadSignal {} +impl> Copy for ReadSignal {} -impl> PartialEq for ReadSignal { +impl> PartialEq for ReadSignal { fn eq(&self, other: &Self) -> bool { self.value == other.value } @@ -171,9 +196,7 @@ impl> PartialEq for ReadSignal { impl< T: Default + 'static, - S: CreateReadOnlySignalStorage> - + ReadOnlySignalStorage - + Storage>, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage>, > Default for ReadSignal { fn default() -> Self { @@ -181,9 +204,9 @@ impl< } } -read_impls!(ReadSignal>); +read_impls!(ReadSignal>); -impl> IntoAttributeValue for ReadSignal +impl> IntoAttributeValue for ReadSignal where T: Clone + IntoAttributeValue + 'static, { @@ -195,14 +218,14 @@ where impl IntoDynNode for ReadSignal where T: Clone + IntoDynNode + 'static, - S: ReadOnlySignalStorage, + S: BoxedSignalStorage, { fn into_dyn_node(self) -> dioxus_core::DynamicNode { self.with(|f| f.clone().into_dyn_node()) } } -impl> Deref for ReadSignal { +impl> Deref for ReadSignal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { @@ -210,7 +233,7 @@ impl> Deref for ReadSignal } } -impl> Readable for ReadSignal { +impl> Readable for ReadSignal { type Target = T; type Storage = S; @@ -251,9 +274,7 @@ impl> Readable for ReadSignal { // all specific readable types impl< T: 'static, - S: CreateReadOnlySignalStorage> - + ReadOnlySignalStorage - + Storage>, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage>, > From> for ReadSignal { fn from(value: Signal) -> Self { @@ -267,7 +288,7 @@ impl From> for ReadSignal { } impl< T: 'static, - S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage + Storage, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage, > From> for ReadSignal { fn from(value: CopyValue) -> Self { @@ -288,7 +309,7 @@ where O: ?Sized + 'static, V: Readable + 'static, F: Fn(&V::Target) -> &O + 'static, - S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { fn from(value: MappedSignal) -> Self { Self::new_maybe_sync(value) @@ -300,7 +321,7 @@ where V: Readable + 'static, F: Fn(&V::Target) -> &O + 'static, FMut: 'static, - S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { fn from(value: MappedMutSignal) -> Self { Self::new_maybe_sync(value) @@ -308,7 +329,7 @@ where } impl From> for ReadSignal where - S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { fn from(value: WriteSignal) -> Self { Self::new_maybe_sync(value) @@ -316,8 +337,8 @@ where } /// A boxed version of [Writable] that can be used to store any writable type. -pub struct WriteSignal = UnsyncStorage> { - value: CopyValue, S>, +pub struct WriteSignal = UnsyncStorage> { + value: CopyValue>, S>, } impl WriteSignal { @@ -329,12 +350,12 @@ impl WriteSignal { } } -impl> WriteSignal { +impl> WriteSignal { /// Create a new boxed writable value which may be sync pub fn new_maybe_sync(value: R) -> Self where R: Writable, - S: CreateReadOnlySignalStorage, + S: CreateBoxedSignalStorage, { Self { value: CopyValue::new_maybe_sync(S::new_writable(value, sealed::SealedToken)), @@ -402,27 +423,27 @@ where } } -impl> Clone for WriteSignal { +impl> Clone for WriteSignal { fn clone(&self) -> Self { *self } } -impl> Copy for WriteSignal {} +impl> Copy for WriteSignal {} -impl> PartialEq for WriteSignal { +impl> PartialEq for WriteSignal { fn eq(&self, other: &Self) -> bool { self.value == other.value } } -read_impls!(WriteSignal>); -write_impls!(WriteSignal>); +read_impls!(WriteSignal>); +write_impls!(WriteSignal>); impl IntoAttributeValue for WriteSignal where T: Clone + IntoAttributeValue + 'static, - S: ReadOnlySignalStorage, + S: BoxedSignalStorage, { fn into_value(self) -> dioxus_core::AttributeValue { self.with(|f| f.clone().into_value()) @@ -432,14 +453,14 @@ where impl IntoDynNode for WriteSignal where T: Clone + IntoDynNode + 'static, - S: ReadOnlySignalStorage, + S: BoxedSignalStorage, { fn into_dyn_node(self) -> dioxus_core::DynamicNode { self.with(|f| f.clone().into_dyn_node()) } } -impl> Deref for WriteSignal { +impl> Deref for WriteSignal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { @@ -447,7 +468,7 @@ impl> Deref for WriteSignal> Readable for WriteSignal { +impl> Readable for WriteSignal { type Target = T; type Storage = S; @@ -483,7 +504,7 @@ impl> Readable for WriteSignal { } } -impl> Writable for WriteSignal { +impl> Writable for WriteSignal { type WriteMetadata = Box; fn try_write_unchecked( @@ -504,9 +525,7 @@ impl> Writable for WriteSignal { // all specific readable types impl< T: 'static, - S: CreateReadOnlySignalStorage> - + ReadOnlySignalStorage - + Storage>, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage>, > From> for WriteSignal { fn from(value: Signal) -> Self { @@ -515,7 +534,7 @@ impl< } impl< T: 'static, - S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage + Storage, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage, > From> for WriteSignal { fn from(value: CopyValue) -> Self { @@ -537,7 +556,7 @@ where V: Writable + 'static, F: Fn(&V::Target) -> &O + 'static, FMut: Fn(&mut V::Target) -> &mut O + 'static, - S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage, + S: CreateBoxedSignalStorage> + BoxedSignalStorage, { fn from(value: MappedMutSignal) -> Self { Self::new_maybe_sync(value) diff --git a/packages/stores/src/store.rs b/packages/stores/src/store.rs index fe56fbdf81..cfc420ed54 100644 --- a/packages/stores/src/store.rs +++ b/packages/stores/src/store.rs @@ -6,9 +6,9 @@ use dioxus_core::{ use_hook, AttributeValue, DynamicNode, IntoAttributeValue, IntoDynNode, Subscribers, SuperInto, }; use dioxus_signals::{ - read_impls, write_impls, BorrowError, BorrowMutError, CopyValue, CreateReadOnlySignalStorage, - Global, InitializeFromFunction, MappedMutSignal, ReadOnlySignalStorage, ReadSignal, Readable, - ReadableExt, ReadableRef, Storage, UnsyncStorage, Writable, WritableExt, WritableRef, + read_impls, write_impls, BorrowError, BorrowMutError, BoxedSignalStorage, CopyValue, + CreateBoxedSignalStorage, Global, InitializeFromFunction, MappedMutSignal, ReadSignal, + Readable, ReadableExt, ReadableRef, Storage, UnsyncStorage, Writable, WritableExt, WritableRef, WriteSignal, }; use std::marker::PhantomData; @@ -188,8 +188,7 @@ where Lens: Writable + 'static, __F: Fn(&Lens::Target) -> &T + 'static, __FMut: Fn(&mut Lens::Target) -> &mut T + 'static, - S: ReadOnlySignalStorage - + CreateReadOnlySignalStorage>, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, T: 'static, { fn from(value: MappedStore) -> Self { @@ -205,8 +204,7 @@ where Lens: Writable + 'static, __F: Fn(&Lens::Target) -> &T + 'static, __FMut: Fn(&mut Lens::Target) -> &mut T + 'static, - S: ReadOnlySignalStorage - + CreateReadOnlySignalStorage>, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, T: 'static, { fn from(value: MappedStore) -> Self { @@ -219,7 +217,7 @@ where impl ::std::convert::From> for ReadStore where T: ?Sized + 'static, - S: ReadOnlySignalStorage + CreateReadOnlySignalStorage>, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { fn from(value: Store>) -> Self { Self { @@ -231,7 +229,7 @@ where impl ::std::convert::From>> for ReadStore where T: 'static, - S: ReadOnlySignalStorage + CreateReadOnlySignalStorage> + Storage, + S: BoxedSignalStorage + CreateBoxedSignalStorage> + Storage, { fn from(value: Store>) -> Self { Self { @@ -243,7 +241,7 @@ where impl ::std::convert::From>> for WriteStore where T: 'static, - S: ReadOnlySignalStorage + CreateReadOnlySignalStorage> + Storage, + S: BoxedSignalStorage + CreateBoxedSignalStorage> + Storage, { fn from(value: Store>) -> Self { Self { @@ -259,7 +257,7 @@ impl SuperInto, SuperIntoReadSignalMarker> for Stor where T: ?Sized + 'static, Lens: Readable + 'static, - S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage, + S: CreateBoxedSignalStorage> + BoxedSignalStorage, { fn super_into(self) -> ReadSignal { ReadSignal::new_maybe_sync(self) @@ -272,7 +270,7 @@ impl SuperInto, SuperIntoWriteSignalMarker> for St where T: ?Sized + 'static, Lens: Writable + 'static, - S: CreateReadOnlySignalStorage> + ReadOnlySignalStorage, + S: CreateBoxedSignalStorage> + BoxedSignalStorage, { fn super_into(self) -> WriteSignal { WriteSignal::new_maybe_sync(self) From bb838f6ecc12c02b3270565f6095158299b0b111 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 25 Aug 2025 11:50:02 -0500 Subject: [PATCH 3/4] minimize diff --- packages/signals/examples/send.rs | 14 ++++---------- packages/signals/src/write.rs | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/signals/examples/send.rs b/packages/signals/examples/send.rs index b55d2952f9..3591d9f058 100644 --- a/packages/signals/examples/send.rs +++ b/packages/signals/examples/send.rs @@ -7,15 +7,6 @@ fn main() { fn app() -> Element { let mut signal = use_signal_sync(|| 0); - rsx! { - button { onclick: move |_| signal += 1, "Increase" } - "{signal}" - Child { signal } - } -} - -#[component] -fn Child(signal: WriteSignal) -> Element { use_hook(|| { std::thread::spawn(move || loop { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -23,5 +14,8 @@ fn Child(signal: WriteSignal) -> Element { }); }); - rsx! {} + rsx! { + button { onclick: move |_| signal += 1, "Increase" } + "{signal}" + } } diff --git a/packages/signals/src/write.rs b/packages/signals/src/write.rs index 098ee6561d..461ca6ef95 100644 --- a/packages/signals/src/write.rs +++ b/packages/signals/src/write.rs @@ -59,7 +59,7 @@ pub trait Writable: Readable { /// # use dioxus::prelude::*; /// fn app() -> Element { /// let mut value = use_signal(|| String::from("hello")); -/// +/// /// rsx! { /// button { /// onclick: move |_| { From c6cb92f98fda29f81b98c572e4f6ece1e956cd49 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 3 Oct 2025 20:05:27 -0700 Subject: [PATCH 4/4] change order for clairty --- packages/signals/examples/send_store.rs | 6 + packages/signals/src/boxed.rs | 224 ++++++++++++------------ 2 files changed, 118 insertions(+), 112 deletions(-) diff --git a/packages/signals/examples/send_store.rs b/packages/signals/examples/send_store.rs index 60768c6c21..9b6f807e39 100644 --- a/packages/signals/examples/send_store.rs +++ b/packages/signals/examples/send_store.rs @@ -16,10 +16,16 @@ fn app() -> Element { #[component] fn Child(store: WriteStore) -> Element { + // A `WriteStore` can be sent to another thread! + // Make sure to clean up your threads when the component unmounts. use_hook(|| { std::thread::spawn(move || loop { std::thread::sleep(std::time::Duration::from_secs(1)); store += 1; + + if store() >= 10 { + break; + } }); }); diff --git a/packages/signals/src/boxed.rs b/packages/signals/src/boxed.rs index f5a6a9bf03..8868623b37 100644 --- a/packages/signals/src/boxed.rs +++ b/packages/signals/src/boxed.rs @@ -16,118 +16,6 @@ use crate::{ )] pub type ReadOnlySignal = ReadSignal; -mod sealed { - use generational_box::{SyncStorage, UnsyncStorage}; - - pub trait Sealed {} - impl Sealed for UnsyncStorage {} - impl Sealed for SyncStorage {} - - pub struct SealedToken; - - pub trait SealedTokenTrait {} - impl SealedTokenTrait for SealedToken {} -} - -/// A trait for creating boxed readable and writable signals. This is implemented for -/// [UnsyncStorage] and [SyncStorage]. -/// -/// You may need to add this trait as a bound when you use [ReadSignal] or [WriteSignal] while -/// remaining generic over syncness. -pub trait BoxedSignalStorage: - Storage>> - + Storage>> - + sealed::Sealed - + 'static -{ - // This is not a public api, and is sealed to prevent external usage and implementations - #[doc(hidden)] - type DynReadable: Readable + ?Sized; - // This is not a public api, and is sealed to prevent external usage and implementations - #[doc(hidden)] - type DynWritable: Writable> - + ?Sized; -} - -/// A trait for creating boxed readable and writable signals. This is implemented for -/// [UnsyncStorage] and [SyncStorage]. -/// -/// The storage type must implement `CreateReadOnlySignalStorage` for every readable `T` type -/// to be used with `ReadSignal` and `WriteSignal`. -/// -/// You may need to add this trait as a bound when you call [ReadSignal::new_maybe_sync] or -/// [WriteSignal::new_maybe_sync] while remaining generic over syncness. -pub trait CreateBoxedSignalStorage: - BoxedSignalStorage + 'static -{ - // This is not a public api, and is sealed to prevent external usage and implementations - #[doc(hidden)] - fn new_readable( - value: T, - _: sealed::SealedToken, - ) -> Box> - where - T: Sized; - - // This is not a public api, and is sealed to prevent external usage and implementations - #[doc(hidden)] - fn new_writable( - value: T, - _: sealed::SealedToken, - ) -> Box> - where - T: Writable + Sized; -} - -impl BoxedSignalStorage for UnsyncStorage { - type DynReadable = dyn Readable; - type DynWritable = - dyn Writable>; -} - -impl + ?Sized + 'static> CreateBoxedSignalStorage - for UnsyncStorage -{ - fn new_readable(value: T, _: sealed::SealedToken) -> Box> - where - T: Sized, - { - Box::new(value) - } - - fn new_writable(value: T, _: sealed::SealedToken) -> Box> - where - T: Writable + Sized, - { - Box::new(BoxWriteMetadata::new(value)) - } -} - -impl BoxedSignalStorage for SyncStorage { - type DynReadable = - dyn Readable + Send + Sync; - type DynWritable = - dyn Writable> + Send + Sync; -} - -impl + Sync + Send + ?Sized + 'static> - CreateBoxedSignalStorage for SyncStorage -{ - fn new_readable(value: T, _: sealed::SealedToken) -> Box> - where - T: Sized, - { - Box::new(value) - } - - fn new_writable(value: T, _: sealed::SealedToken) -> Box> - where - T: Writable + Sized, - { - Box::new(BoxWriteMetadata::new(value)) - } -} - /// A boxed version of [Readable] that can be used to store any readable type. pub struct ReadSignal = UnsyncStorage> { value: CopyValue>, S>, @@ -562,3 +450,115 @@ where Self::new_maybe_sync(value) } } + +/// A trait for creating boxed readable and writable signals. This is implemented for +/// [UnsyncStorage] and [SyncStorage]. +/// +/// You may need to add this trait as a bound when you use [ReadSignal] or [WriteSignal] while +/// remaining generic over syncness. +pub trait BoxedSignalStorage: + Storage>> + + Storage>> + + sealed::Sealed + + 'static +{ + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + type DynReadable: Readable + ?Sized; + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + type DynWritable: Writable> + + ?Sized; +} + +/// A trait for creating boxed readable and writable signals. This is implemented for +/// [UnsyncStorage] and [SyncStorage]. +/// +/// The storage type must implement `CreateReadOnlySignalStorage` for every readable `T` type +/// to be used with `ReadSignal` and `WriteSignal`. +/// +/// You may need to add this trait as a bound when you call [ReadSignal::new_maybe_sync] or +/// [WriteSignal::new_maybe_sync] while remaining generic over syncness. +pub trait CreateBoxedSignalStorage: + BoxedSignalStorage + 'static +{ + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + fn new_readable( + value: T, + _: sealed::SealedToken, + ) -> Box> + where + T: Sized; + + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + fn new_writable( + value: T, + _: sealed::SealedToken, + ) -> Box> + where + T: Writable + Sized; +} + +impl BoxedSignalStorage for UnsyncStorage { + type DynReadable = dyn Readable; + type DynWritable = + dyn Writable>; +} + +impl + ?Sized + 'static> CreateBoxedSignalStorage + for UnsyncStorage +{ + fn new_readable(value: T, _: sealed::SealedToken) -> Box> + where + T: Sized, + { + Box::new(value) + } + + fn new_writable(value: T, _: sealed::SealedToken) -> Box> + where + T: Writable + Sized, + { + Box::new(BoxWriteMetadata::new(value)) + } +} + +impl BoxedSignalStorage for SyncStorage { + type DynReadable = + dyn Readable + Send + Sync; + type DynWritable = + dyn Writable> + Send + Sync; +} + +impl + Sync + Send + ?Sized + 'static> + CreateBoxedSignalStorage for SyncStorage +{ + fn new_readable(value: T, _: sealed::SealedToken) -> Box> + where + T: Sized, + { + Box::new(value) + } + + fn new_writable(value: T, _: sealed::SealedToken) -> Box> + where + T: Writable + Sized, + { + Box::new(BoxWriteMetadata::new(value)) + } +} + +mod sealed { + use generational_box::{SyncStorage, UnsyncStorage}; + + pub trait Sealed {} + impl Sealed for UnsyncStorage {} + impl Sealed for SyncStorage {} + + pub struct SealedToken; + + pub trait SealedTokenTrait {} + impl SealedTokenTrait for SealedToken {} +}