Ver Fonte

Merge pull request #1550 from ealmloff/restore-deref-fn-signal

Make signal callable on stable
Jonathan Kelley há 1 ano atrás
pai
commit
025a321648

+ 4 - 4
packages/generational-box/src/lib.rs

@@ -184,7 +184,7 @@ impl<T: 'static> GenerationalBox<T> {
     }
 
     /// Try to read the value. Returns None if the value is no longer valid.
-    pub fn try_read(&self) -> Option<Ref<'_, T>> {
+    pub fn try_read(&self) -> Option<Ref<'static, T>> {
         self.validate()
             .then(|| {
                 Ref::filter_map(self.raw.data.borrow(), |any| {
@@ -196,12 +196,12 @@ impl<T: 'static> GenerationalBox<T> {
     }
 
     /// Read the value. Panics if the value is no longer valid.
-    pub fn read(&self) -> Ref<'_, T> {
+    pub fn read(&self) -> Ref<'static, T> {
         self.try_read().unwrap()
     }
 
     /// Try to write the value. Returns None if the value is no longer valid.
-    pub fn try_write(&self) -> Option<RefMut<'_, T>> {
+    pub fn try_write(&self) -> Option<RefMut<'static, T>> {
         self.validate()
             .then(|| {
                 RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
@@ -213,7 +213,7 @@ impl<T: 'static> GenerationalBox<T> {
     }
 
     /// Write the value. Panics if the value is no longer valid.
-    pub fn write(&self) -> RefMut<'_, T> {
+    pub fn write(&self) -> RefMut<'static, T> {
         self.try_write().unwrap()
     }
 

+ 38 - 3
packages/signals/src/rt.rs

@@ -1,5 +1,7 @@
 use std::cell::{Ref, RefMut};
 
+use std::mem::MaybeUninit;
+use std::ops::Deref;
 use std::rc::Rc;
 
 use dioxus_core::prelude::*;
@@ -120,17 +122,17 @@ impl<T: 'static> CopyValue<T> {
     }
 
     /// Read the value. If the value has been dropped, this will panic.
-    pub fn read(&self) -> Ref<'_, T> {
+    pub fn read(&self) -> Ref<'static, T> {
         self.value.read()
     }
 
     /// Try to write the value. If the value has been dropped, this will return None.
-    pub fn try_write(&self) -> Option<RefMut<'_, T>> {
+    pub fn try_write(&self) -> Option<RefMut<'static, T>> {
         self.value.try_write()
     }
 
     /// Write the value. If the value has been dropped, this will panic.
-    pub fn write(&self) -> RefMut<'_, T> {
+    pub fn write(&self) -> RefMut<'static, T> {
         self.value.write()
     }
 
@@ -164,3 +166,36 @@ impl<T: 'static> PartialEq for CopyValue<T> {
         self.value.ptr_eq(&other.value)
     }
 }
+
+impl<T> Deref for CopyValue<T> {
+    type Target = dyn Fn() -> Ref<'static, 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
+    }
+}

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

@@ -1,5 +1,6 @@
 use std::{
     cell::{Ref, RefCell, RefMut},
+    mem::MaybeUninit,
     ops::{Deref, DerefMut},
     rc::Rc,
     sync::Arc,
@@ -268,6 +269,39 @@ impl<T: 'static> PartialEq for Signal<T> {
     }
 }
 
+impl<T> Deref for Signal<T> {
+    type Target = dyn Fn() -> Ref<'static, 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
+    }
+}
+
 struct SignalSubscriberDrop<T: 'static> {
     signal: Signal<T>,
 }
@@ -358,3 +392,36 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
         self.inner == other.inner
     }
 }
+
+impl<T> Deref for ReadOnlySignal<T> {
+    type Target = dyn Fn() -> Ref<'static, 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
+    }
+}

+ 24 - 0
packages/signals/tests/create.rs

@@ -29,6 +29,30 @@ fn create_signals_global() {
     }
 }
 
+#[test]
+fn deref_signal() {
+    let mut dom = VirtualDom::new(|cx| {
+        render! {
+            for _ in 0..10 {
+                Child {}
+            }
+        }
+    });
+
+    fn Child(cx: Scope) -> Element {
+        let signal = Signal::new("hello world".to_string());
+
+        // You can call signals like functions to get a Ref of their value.
+        assert_eq!(&*signal(), "hello world");
+
+        render! {
+            "hello world"
+        }
+    }
+
+    let _edits = dom.rebuild().santize();
+}
+
 #[test]
 fn drop_signals() {
     let mut dom = VirtualDom::new(|cx| {