Browse Source

Merge pull request #2023 from ealmloff/hooks-readable

Implement Readable for Hooks
Jonathan Kelley 1 năm trước cách đây
mục cha
commit
ac990a4447

+ 1 - 0
Cargo.lock

@@ -2492,6 +2492,7 @@ dependencies = [
  "dioxus-signals",
  "futures-channel",
  "futures-util",
+ "generational-box",
  "slab",
  "thiserror",
  "tokio",

+ 1 - 1
packages/desktop/src/hooks.rs

@@ -58,7 +58,7 @@ pub fn use_global_shortcut(
     handler: impl FnMut() + 'static,
 ) -> Result<ShortcutHandle, ShortcutRegistryError> {
     // wrap the user's handler in something that will carry the scope/runtime with it
-    let mut cb = use_callback(handler);
+    let cb = use_callback(handler);
 
     use_hook_with_cleanup(
         move || window().create_shortcut(accelerator.accelerator(), move || cb.call()),

+ 1 - 1
packages/fullstack/src/hooks/server_future.rs

@@ -9,7 +9,7 @@ where
     T: Serialize + DeserializeOwned + 'static,
     F: Future<Output = T> + 'static,
 {
-    let mut cb = use_callback(_future);
+    let cb = use_callback(_future);
     let mut first_run = use_hook(|| CopyValue::new(true));
 
     let resource = use_resource(move || {

+ 1 - 0
packages/hooks/Cargo.toml

@@ -22,6 +22,7 @@ thiserror = { workspace = true }
 slab = { workspace = true }
 dioxus-debug-cell = "0.1.1"
 futures-util = { workspace = true}
+generational-box.workspace = true
 
 [dev-dependencies]
 futures-util = { workspace = true, default-features = false }

+ 38 - 2
packages/hooks/src/use_callback.rs

@@ -49,7 +49,43 @@ impl<O: 'static> Copy for UseCallback<O> {}
 
 impl<O> UseCallback<O> {
     /// Call the callback
-    pub fn call(&mut self) -> O {
-        self.inner.with_mut(|f| f())
+    pub fn call(&self) -> O {
+        (self.inner.write_unchecked())()
+    }
+}
+
+// This makes UseCallback callable like a normal function
+impl<O> std::ops::Deref for UseCallback<O> {
+    type Target = dyn Fn() -> O;
+
+    fn deref(&self) -> &Self::Target {
+        use std::mem::MaybeUninit;
+
+        // https://github.com/dtolnay/case-studies/tree/master/callable-types
+
+        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
+        let uninit_callable = MaybeUninit::<Self>::uninit();
+        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
+        let uninit_closure = move || Self::call(unsafe { &*uninit_callable.as_ptr() });
+
+        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
+        let size_of_closure = std::mem::size_of_val(&uninit_closure);
+        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
+
+        // Then cast the lifetime of the closure to the lifetime of &self.
+        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
+            b
+        }
+        let reference_to_closure = cast_lifetime(
+            {
+                // The real closure that we will never use.
+                &uninit_closure
+            },
+            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
+            unsafe { std::mem::transmute(self) },
+        );
+
+        // Cast the closure to a trait object.
+        reference_to_closure as &_
     }
 }

+ 36 - 1
packages/hooks/src/use_future.rs

@@ -4,6 +4,7 @@ use dioxus_core::{prelude::*, Task};
 use dioxus_signals::*;
 use dioxus_signals::{Readable, Writable};
 use std::future::Future;
+use std::ops::Deref;
 
 /// A hook that allows you to spawn a future.
 /// This future will **not** run on the server
@@ -39,7 +40,7 @@ where
 {
     let mut state = use_signal(|| UseFutureState::Pending);
 
-    let mut callback = use_callback(move || {
+    let callback = use_callback(move || {
         let fut = future();
         spawn(async move {
             state.set(UseFutureState::Pending);
@@ -147,3 +148,37 @@ impl UseFuture {
         self.state.into()
     }
 }
+
+impl From<UseFuture> for ReadOnlySignal<UseFutureState> {
+    fn from(val: UseFuture) -> Self {
+        val.state.into()
+    }
+}
+
+impl Readable for UseFuture {
+    type Target = UseFutureState;
+    type Storage = UnsyncStorage;
+
+    #[track_caller]
+    fn try_read_unchecked(
+        &self,
+    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
+        self.state.try_read_unchecked()
+    }
+
+    #[track_caller]
+    fn peek_unchecked(&self) -> ReadableRef<'static, Self> {
+        self.state.peek_unchecked()
+    }
+}
+
+/// Allow calling a signal with signal() syntax
+///
+/// Currently only limited to copy types, though could probably specialize for string/arc/rc
+impl Deref for UseFuture {
+    type Target = dyn Fn() -> UseFutureState;
+
+    fn deref(&self) -> &Self::Target {
+        Readable::deref_impl(self)
+    }
+}

+ 3 - 2
packages/hooks/src/use_memo.rs

@@ -25,8 +25,9 @@ use futures_util::StreamExt;
 /// ```
 #[track_caller]
 pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> Memo<R> {
-    let mut callback = use_callback(f);
-    use_hook(|| Signal::memo(move || callback.call()))
+    let callback = use_callback(f);
+    #[allow(clippy::redundant_closure)]
+    use_hook(|| Signal::memo(move || callback()))
 }
 
 /// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes

+ 52 - 6
packages/hooks/src/use_resource.rs

@@ -1,12 +1,14 @@
 #![allow(missing_docs)]
 
 use crate::{use_callback, use_signal, UseCallback};
+use dioxus_core::prelude::*;
 use dioxus_core::{
     prelude::{spawn, use_hook},
     Task,
 };
 use dioxus_signals::*;
 use futures_util::{future, pin_mut, FutureExt, StreamExt};
+use std::ops::Deref;
 use std::{cell::Cell, future::Future, rc::Rc};
 
 /// A memo that resolve to a value asynchronously.
@@ -49,7 +51,7 @@ where
         (rc, Rc::new(Cell::new(Some(changed))))
     });
 
-    let mut cb = use_callback(move || {
+    let cb = use_callback(move || {
         // Create the user's task
         #[allow(clippy::redundant_closure)]
         let fut = rc.run_in(|| future());
@@ -70,7 +72,7 @@ where
         })
     });
 
-    let mut task = use_hook(|| Signal::new(cb.call()));
+    let mut task = use_hook(|| Signal::new(cb()));
 
     use_hook(|| {
         let mut changed = changed.take().unwrap();
@@ -83,7 +85,7 @@ where
                 task.write().cancel();
 
                 // Start a new task
-                task.set(cb.call());
+                task.set(cb());
             }
         })
     });
@@ -181,10 +183,54 @@ impl<T> Resource<T> {
     }
 }
 
-impl<T> std::ops::Deref for Resource<T> {
-    type Target = Signal<Option<T>>;
+impl<T> From<Resource<T>> for ReadOnlySignal<Option<T>> {
+    fn from(val: Resource<T>) -> Self {
+        val.value.into()
+    }
+}
+
+impl<T> Readable for Resource<T> {
+    type Target = Option<T>;
+    type Storage = UnsyncStorage;
+
+    #[track_caller]
+    fn try_read_unchecked(
+        &self,
+    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
+        self.value.try_read_unchecked()
+    }
+
+    #[track_caller]
+    fn peek_unchecked(&self) -> ReadableRef<'static, Self> {
+        self.value.peek_unchecked()
+    }
+}
+
+impl<T> IntoAttributeValue for Resource<T>
+where
+    T: Clone + IntoAttributeValue,
+{
+    fn into_value(self) -> dioxus_core::AttributeValue {
+        self.with(|f| f.clone().into_value())
+    }
+}
+
+impl<T> IntoDynNode for Resource<T>
+where
+    T: Clone + IntoDynNode,
+{
+    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
+        self().into_dyn_node()
+    }
+}
+
+/// Allow calling a signal with signal() syntax
+///
+/// Currently only limited to copy types, though could probably specialize for string/arc/rc
+impl<T: Clone> Deref for Resource<T> {
+    type Target = dyn Fn() -> Option<T>;
 
     fn deref(&self) -> &Self::Target {
-        &self.value
+        Readable::deref_impl(self)
     }
 }

+ 0 - 376
packages/hooks/src/use_shared_state.rs

@@ -1,376 +0,0 @@
-use self::error::{UseSharedStateError, UseSharedStateResult};
-use dioxus_core::ScopeId;
-use std::{collections::HashSet, rc::Rc, sync::Arc};
-
-#[cfg(debug_assertions)]
-pub use dioxus_debug_cell::{
-    error::{BorrowError, BorrowMutError},
-    Ref, RefCell, RefMut,
-};
-
-#[cfg(not(debug_assertions))]
-pub use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut};
-
-#[macro_export]
-macro_rules! debug_location {
-    () => {{
-        #[cfg(debug_assertions)]
-        {
-            std::panic::Location::caller()
-        }
-        #[cfg(not(debug_assertions))]
-        {
-            ()
-        }
-    }};
-}
-
-pub mod error {
-    #[cfg(debug_assertions)]
-    fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String {
-        locations
-            .iter()
-            .map(|location| format!(" - {location}"))
-            .collect::<Vec<_>>()
-            .join("\n")
-    }
-    #[derive(thiserror::Error, Debug)]
-    pub enum UseSharedStateError {
-        #[cfg_attr(
-            debug_assertions,
-            error(
-                "[{0}] {1} is already borrowed at, so it cannot be borrowed mutably. Previous borrows:\n[{2}]\n\n",
-                .source.attempted_at,
-                .type_name,
-                locations_display(&.source.already_borrowed_at)
-            )
-         )]
-        #[cfg_attr(
-            not(debug_assertions),
-            error("{type_name} is already borrowed, so it cannot be borrowed mutably. (More detail available in debug mode)")
-        )]
-        AlreadyBorrowed {
-            source: super::BorrowMutError,
-            type_name: &'static str,
-        },
-        #[cfg_attr(
-            debug_assertions,
-            error(
-                "[{0}] {1} is already borrowed mutably at [{2}], so it cannot be borrowed anymore.",
-                .source.attempted_at,
-                .type_name,
-                locations_display(&.source.already_borrowed_at)
-            )
-         )]
-        #[cfg_attr(
-            not(debug_assertions),
-            error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore. (More detail available in debug mode)")
-        )]
-        AlreadyBorrowedMutably {
-            source: super::BorrowError,
-            type_name: &'static str,
-        },
-    }
-
-    pub type UseSharedStateResult<T> = Result<T, UseSharedStateError>;
-}
-
-type ProvidedState<T> = Rc<RefCell<ProvidedStateInner<T>>>;
-
-// Tracks all the subscribers to a shared State
-pub(crate) struct ProvidedStateInner<T> {
-    value: T,
-    notify_any: Arc<dyn Fn(ScopeId)>,
-    consumers: HashSet<ScopeId>,
-    gen: usize,
-}
-
-impl<T> ProvidedStateInner<T> {
-    pub(crate) fn notify_consumers(&mut self) {
-        self.gen += 1;
-        for consumer in self.consumers.iter() {
-            (self.notify_any)(*consumer);
-        }
-    }
-}
-
-/// This hook provides some relatively light ergonomics around shared state.
-///
-/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
-/// ergonomics in a pinch, with zero cost.
-///
-/// # Example
-///
-/// ```rust
-/// # use dioxus::prelude::*;
-/// #
-/// # fn app() -> Element {
-/// #     rsx! {
-/// #         Parent{}
-/// #     }
-/// # }
-///
-/// #[derive(Clone, Copy)]
-/// enum Theme {
-///     Light,
-///     Dark,
-/// }
-///
-/// // Provider
-/// fn Parent<'a>(cx: Scope<'a>) -> Element {
-///     use_shared_state_provider(|| Theme::Dark);
-///     let theme = use_shared_state::<Theme>().unwrap();
-///
-///     rsx! {
-///         button{
-///             onclick: move |_| {
-///                 let current_theme = *theme.read();
-///                 *theme.write() = match current_theme {
-///                     Theme::Dark => Theme::Light,
-///                     Theme::Light => Theme::Dark,
-///                 };
-///             },
-///             "Change theme"
-///         }
-///         Child{}
-///     }
-/// }
-///
-/// // Consumer
-/// fn Child<'a>(cx: Scope<'a>) -> Element {
-///     let theme = use_shared_state::<Theme>().unwrap();
-///     let current_theme = *theme.read();
-///
-///     rsx! {
-///         match current_theme {
-///             Theme::Dark => {
-///                 "Dark mode"
-///             }
-///             Theme::Light => {
-///                 "Light mode"
-///             }
-///         }
-///     }
-/// }
-/// ```
-///
-/// # How it works
-///
-/// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
-///
-/// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
-#[must_use]
-pub fn use_shared_state<T: 'static>() -> Option<&UseSharedState<T>> {
-    let state_owner: &mut Option<UseSharedStateOwner<T>> = &mut *cx.use_hook(move || {
-        let scope_id = cx.scope_id();
-        let root = cx.consume_context::<ProvidedState<T>>()?;
-
-        root.borrow_mut().consumers.insert(scope_id);
-
-        let state = UseSharedState::new(root);
-        let owner = UseSharedStateOwner { state, scope_id };
-        Some(owner)
-    });
-    state_owner.as_mut().map(|s| {
-        s.state.gen = s.state.inner.borrow().gen;
-        &s.state
-    })
-}
-
-/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted
-struct UseSharedStateOwner<T> {
-    state: UseSharedState<T>,
-    scope_id: ScopeId,
-}
-
-impl<T> Drop for UseSharedStateOwner<T> {
-    fn drop(&mut self) {
-        // we need to unsubscribe when our component is unmounted
-        let mut root = self.state.inner.borrow_mut();
-        root.consumers.remove(&self.scope_id);
-    }
-}
-
-/// State that is shared between components through the context system
-pub struct UseSharedState<T> {
-    pub(crate) inner: Rc<RefCell<ProvidedStateInner<T>>>,
-    gen: usize,
-}
-
-impl<T> UseSharedState<T> {
-    fn new(inner: Rc<RefCell<ProvidedStateInner<T>>>) -> Self {
-        let gen = inner.borrow().gen;
-        Self { inner, gen }
-    }
-
-    /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write")
-    pub fn notify_consumers(&self) {
-        self.inner.borrow_mut().notify_consumers();
-    }
-
-    /// Try reading the shared state
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn try_read(&self) -> UseSharedStateResult<Ref<'_, T>> {
-        match self.inner.try_borrow() {
-            Ok(value) => Ok(Ref::map(value, |inner| &inner.value)),
-            Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably {
-                source,
-                type_name: std::any::type_name::<Self>(),
-            }),
-        }
-    }
-
-    /// Read the shared value
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn read(&self) -> Ref<'_, T> {
-        match self.try_read() {
-            Ok(value) => value,
-            Err(message) => panic!(
-                "Reading the shared state failed: {}\n({:?})",
-                message, message
-            ),
-        }
-    }
-
-    /// Try writing the shared state
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn try_write(&self) -> UseSharedStateResult<RefMut<'_, T>> {
-        match self.inner.try_borrow_mut() {
-            Ok(mut value) => {
-                value.notify_consumers();
-                Ok(RefMut::map(value, |inner| &mut inner.value))
-            }
-            Err(source) => Err(UseSharedStateError::AlreadyBorrowed {
-                source,
-                type_name: std::any::type_name::<Self>(),
-            }),
-        }
-    }
-
-    /// Calling "write" will force the component to re-render
-    ///
-    ///
-    // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn write(&self) -> RefMut<'_, T> {
-        match self.try_write() {
-            Ok(value) => value,
-            Err(message) => panic!(
-                "Writing to shared state failed: {}\n({:?})",
-                message, message
-            ),
-        }
-    }
-
-    /// Tries writing the value without forcing a re-render
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn try_write_silent(&self) -> UseSharedStateResult<RefMut<'_, T>> {
-        match self.inner.try_borrow_mut() {
-            Ok(value) => Ok(RefMut::map(value, |inner| &mut inner.value)),
-            Err(source) => Err(UseSharedStateError::AlreadyBorrowed {
-                source,
-                type_name: std::any::type_name::<Self>(),
-            }),
-        }
-    }
-
-    /// Writes the value without forcing a re-render
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn write_silent(&self) -> RefMut<'_, T> {
-        match self.try_write_silent() {
-            Ok(value) => value,
-            Err(message) => panic!(
-                "Writing to shared state silently failed: {}\n({:?})",
-                message, message
-            ),
-        }
-    }
-
-    /// Take a reference to the inner value temporarily and produce a new value
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
-        immutable_callback(&*self.read())
-    }
-
-    /// Take a mutable reference to the inner value temporarily and produce a new value
-    #[cfg_attr(debug_assertions, track_caller)]
-    #[cfg_attr(debug_assertions, inline(never))]
-    pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
-        mutable_callback(&mut *self.write())
-    }
-}
-
-impl<T> Clone for UseSharedState<T> {
-    fn clone(&self) -> Self {
-        Self {
-            inner: self.inner.clone(),
-            gen: self.gen,
-        }
-    }
-}
-
-impl<T> PartialEq for UseSharedState<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.gen == other.gen
-    }
-}
-
-/// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state
-///
-///
-/// # Example
-///
-/// ```rust
-/// # use dioxus::prelude::*;
-/// #
-/// # fn app() -> Element {
-/// #     rsx! {
-/// #         Parent{}
-/// #     }
-/// # }
-///
-/// #[derive(Clone, Copy)]
-/// enum Theme {
-///     Light,
-///     Dark,
-/// }
-///
-/// // Provider
-/// fn Parent<'a>(cx: Scope<'a>) -> Element {
-///     use_shared_state_provider(|| Theme::Dark);
-///     let theme = use_shared_state::<Theme>().unwrap();
-///
-///     rsx! {
-///         button{
-///             onclick: move |_| {
-///                 let current_theme = *theme.read();
-///                 *theme.write() = match current_theme {
-///                     Theme::Dark => Theme::Light,
-///                     Theme::Light => Theme::Dark,
-///                 };
-///             },
-///             "Change theme"
-///         }
-///         // Children components that consume the state...
-///     }
-/// }
-/// ```
-pub fn use_shared_state_provider<T: 'static>(f: impl FnOnce() -> T) {
-    cx.use_hook(|| {
-        let state: ProvidedState<T> = Rc::new(RefCell::new(ProvidedStateInner {
-            value: f(),
-            notify_any: cx.schedule_update_any(),
-            consumers: HashSet::new(),
-            gen: 0,
-        }));
-
-        cx.provide_context(state);
-    });
-}

+ 1 - 27
packages/signals/src/copy_value.rs

@@ -5,7 +5,6 @@ use generational_box::UnsyncStorage;
 use std::any::Any;
 use std::any::TypeId;
 use std::cell::RefCell;
-use std::mem::MaybeUninit;
 use std::ops::Deref;
 
 use dioxus_core::prelude::*;
@@ -268,31 +267,6 @@ impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
     type Target = dyn Fn() -> T;
 
     fn deref(&self) -> &Self::Target {
-        // https://github.com/dtolnay/case-studies/tree/master/callable-types
-
-        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
-        let uninit_callable = MaybeUninit::<Self>::uninit();
-        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
-        let uninit_closure = move || *Self::read(unsafe { &*uninit_callable.as_ptr() });
-
-        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
-        let size_of_closure = std::mem::size_of_val(&uninit_closure);
-        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
-
-        // Then cast the lifetime of the closure to the lifetime of &self.
-        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
-            b
-        }
-        let reference_to_closure = cast_lifetime(
-            {
-                // The real closure that we will never use.
-                &uninit_closure
-            },
-            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
-            unsafe { std::mem::transmute(self) },
-        );
-
-        // Cast the closure to a trait object.
-        reference_to_closure as &Self::Target
+        Readable::deref_impl(self)
     }
 }

+ 3 - 37
packages/signals/src/global/memo.rs

@@ -1,7 +1,7 @@
 use crate::{read::Readable, Memo, ReadableRef};
-use dioxus_core::prelude::{IntoAttributeValue, ScopeId};
+use dioxus_core::prelude::ScopeId;
 use generational_box::UnsyncStorage;
-use std::{mem::MaybeUninit, ops::Deref};
+use std::ops::Deref;
 
 use crate::Signal;
 
@@ -68,15 +68,6 @@ impl<T: PartialEq + 'static> Readable for GlobalMemo<T> {
     }
 }
 
-impl<T: PartialEq + 'static> IntoAttributeValue for GlobalMemo<T>
-where
-    T: Clone + IntoAttributeValue,
-{
-    fn into_value(self) -> dioxus_core::AttributeValue {
-        self.memo().into_value()
-    }
-}
-
 impl<T: PartialEq + 'static> PartialEq for GlobalMemo<T> {
     fn eq(&self, other: &Self) -> bool {
         std::ptr::eq(self, other)
@@ -90,31 +81,6 @@ impl<T: PartialEq + Clone + 'static> Deref for GlobalMemo<T> {
     type Target = dyn Fn() -> T;
 
     fn deref(&self) -> &Self::Target {
-        // https://github.com/dtolnay/case-studies/tree/master/callable-types
-
-        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
-        let uninit_callable = MaybeUninit::<Self>::uninit();
-        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
-        let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
-
-        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
-        let size_of_closure = std::mem::size_of_val(&uninit_closure);
-        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
-
-        // Then cast the lifetime of the closure to the lifetime of &self.
-        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
-            b
-        }
-        let reference_to_closure = cast_lifetime(
-            {
-                // The real closure that we will never use.
-                &uninit_closure
-            },
-            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self.
-            unsafe { std::mem::transmute(self) },
-        );
-
-        // Cast the closure to a trait object.
-        reference_to_closure as &Self::Target
+        Readable::deref_impl(self)
     }
 }

+ 3 - 40
packages/signals/src/global/signal.rs

@@ -1,9 +1,9 @@
 use crate::write::Writable;
 use crate::{read::Readable, ReadableRef};
 use crate::{WritableRef, Write};
-use dioxus_core::prelude::{IntoAttributeValue, ScopeId};
+use dioxus_core::prelude::ScopeId;
 use generational_box::UnsyncStorage;
-use std::{mem::MaybeUninit, ops::Deref};
+use std::ops::Deref;
 
 use super::get_global_context;
 use crate::Signal;
@@ -118,15 +118,6 @@ impl<T: 'static> Writable for GlobalSignal<T> {
     }
 }
 
-impl<T: 'static> IntoAttributeValue for GlobalSignal<T>
-where
-    T: Clone + IntoAttributeValue,
-{
-    fn into_value(self) -> dioxus_core::AttributeValue {
-        self.signal().into_value()
-    }
-}
-
 impl<T: 'static> PartialEq for GlobalSignal<T> {
     fn eq(&self, other: &Self) -> bool {
         std::ptr::eq(self, other)
@@ -140,34 +131,6 @@ impl<T: Clone + 'static> Deref for GlobalSignal<T> {
     type Target = dyn Fn() -> T;
 
     fn deref(&self) -> &Self::Target {
-        // https://github.com/dtolnay/case-studies/tree/master/callable-types
-
-        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
-        let uninit_callable = MaybeUninit::<Self>::uninit();
-
-        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
-        let uninit_closure = move || {
-            <GlobalSignal<T> as Readable>::read(unsafe { &*uninit_callable.as_ptr() }).clone()
-        };
-
-        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
-        let size_of_closure = std::mem::size_of_val(&uninit_closure);
-        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
-
-        // Then cast the lifetime of the closure to the lifetime of &self.
-        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
-            b
-        }
-        let reference_to_closure = cast_lifetime(
-            {
-                // The real closure that we will never use.
-                &uninit_closure
-            },
-            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self.
-            unsafe { std::mem::transmute(self) },
-        );
-
-        // Cast the closure to a trait object.
-        reference_to_closure as &Self::Target
+        Readable::deref_impl(self)
     }
 }

+ 9 - 0
packages/signals/src/memo.rs

@@ -204,6 +204,15 @@ where
     }
 }
 
+impl<T> IntoDynNode for Memo<T>
+where
+    T: Clone + IntoDynNode + PartialEq,
+{
+    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
+        self().into_dyn_node()
+    }
+}
+
 impl<T: 'static> PartialEq for Memo<T> {
     fn eq(&self, other: &Self) -> bool {
         self.inner == other.inner

+ 10 - 0
packages/signals/src/read_only_signal.rs

@@ -1,4 +1,5 @@
 use crate::{read::Readable, ReadableRef, Signal, SignalData};
+use dioxus_core::IntoDynNode;
 use std::ops::Deref;
 
 use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
@@ -105,6 +106,15 @@ where
     }
 }
 
+impl<T> IntoDynNode for ReadOnlySignal<T>
+where
+    T: Clone + IntoDynNode,
+{
+    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
+        self().into_dyn_node()
+    }
+}
+
 impl<T: 'static, S: Storage<SignalData<T>>> PartialEq for ReadOnlySignal<T, S> {
     fn eq(&self, other: &Self) -> bool {
         self.inner == other.inner

+ 10 - 0
packages/signals/src/signal.rs

@@ -3,6 +3,7 @@ use crate::{
     ReadableRef,
 };
 use crate::{Memo, WritableRef};
+use dioxus_core::IntoDynNode;
 use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
 use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage};
 use std::{
@@ -245,6 +246,15 @@ where
     }
 }
 
+impl<T> IntoDynNode for Signal<T>
+where
+    T: Clone + IntoDynNode,
+{
+    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
+        self().into_dyn_node()
+    }
+}
+
 impl<T: 'static, S: Storage<SignalData<T>>> PartialEq for Signal<T, S> {
     fn eq(&self, other: &Self) -> bool {
         self.inner == other.inner