فهرست منبع

Merge pull request #1383 from ealmloff/debug-signals

Add debug information to borrows and ownership in signals
Jonathan Kelley 1 سال پیش
والد
کامیت
ccd850edf6

+ 2 - 0
packages/generational-box/Cargo.toml

@@ -18,3 +18,5 @@ rand = "0.8.5"
 [features]
 default = ["check_generation"]
 check_generation = []
+debug_borrows = []
+debug_ownership = []

+ 2 - 0
packages/generational-box/README.md

@@ -11,6 +11,8 @@ Three main types manage state in Generational Box:
 Example:
 
 ```rust
+use generational_box::Store;
+
 // Create a store for this thread
 let store = Store::default();
 

+ 407 - 46
packages/generational-box/src/lib.rs

@@ -2,9 +2,12 @@
 #![warn(missing_docs)]
 
 use std::{
+    any::Any,
     cell::{Cell, Ref, RefCell, RefMut},
-    fmt::Debug,
+    error::Error,
+    fmt::{Debug, Display},
     marker::PhantomData,
+    ops::{Deref, DerefMut},
     rc::Rc,
 };
 
@@ -29,12 +32,12 @@ fn reused() {
     let first_ptr;
     {
         let owner = store.owner();
-        first_ptr = owner.insert(1).raw.data.as_ptr();
+        first_ptr = owner.insert(1).raw.0.data.as_ptr();
         drop(owner);
     }
     {
         let owner = store.owner();
-        let second_ptr = owner.insert(1234).raw.data.as_ptr();
+        let second_ptr = owner.insert(1234).raw.0.data.as_ptr();
         assert_eq!(first_ptr, second_ptr);
         drop(owner);
     }
@@ -53,7 +56,10 @@ fn leaking_is_ok() {
         // don't drop the owner
         std::mem::forget(owner);
     }
-    assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string()));
+    assert_eq!(
+        key.try_read().as_deref().unwrap(),
+        &"hello world".to_string()
+    );
 }
 
 #[test]
@@ -68,7 +74,7 @@ fn drops() {
         key = owner.insert(data);
         // drop the owner
     }
-    assert!(key.try_read().is_none());
+    assert!(key.try_read().is_err());
 }
 
 #[test]
@@ -129,7 +135,7 @@ fn fuzz() {
             println!("{:?}", path);
             for key in valid_keys.iter() {
                 let value = key.read();
-                println!("{:?}", value);
+                println!("{:?}", &*value);
                 assert!(value.starts_with("hello world"));
             }
             #[cfg(any(debug_assertions, feature = "check_generation"))]
@@ -153,6 +159,8 @@ pub struct GenerationalBox<T> {
     raw: MemoryLocation,
     #[cfg(any(debug_assertions, feature = "check_generation"))]
     generation: u32,
+    #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+    created_at: &'static std::panic::Location<'static>,
     _marker: PhantomData<T>,
 }
 
@@ -161,7 +169,7 @@ impl<T: 'static> Debug for GenerationalBox<T> {
         #[cfg(any(debug_assertions, feature = "check_generation"))]
         f.write_fmt(format_args!(
             "{:?}@{:?}",
-            self.raw.data.as_ptr(),
+            self.raw.0.data.as_ptr(),
             self.generation
         ))?;
         #[cfg(not(any(debug_assertions, feature = "check_generation")))]
@@ -175,7 +183,7 @@ impl<T: 'static> GenerationalBox<T> {
     fn validate(&self) -> bool {
         #[cfg(any(debug_assertions, feature = "check_generation"))]
         {
-            self.raw.generation.get() == self.generation
+            self.raw.0.generation.get() == self.generation
         }
         #[cfg(not(any(debug_assertions, feature = "check_generation")))]
         {
@@ -184,43 +192,51 @@ 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<'static, T>> {
-        self.validate()
-            .then(|| {
-                Ref::filter_map(self.raw.data.borrow(), |any| {
-                    any.as_ref()?.downcast_ref::<T>()
-                })
-                .ok()
-            })
-            .flatten()
+    #[track_caller]
+    pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
+        if !self.validate() {
+            return Err(BorrowError::Dropped(ValueDroppedError {
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                created_at: self.created_at,
+            }));
+        }
+        self.raw.try_borrow(
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            self.created_at,
+        )
     }
 
     /// Read the value. Panics if the value is no longer valid.
-    pub fn read(&self) -> Ref<'static, T> {
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<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<'static, T>> {
-        self.validate()
-            .then(|| {
-                RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
-                    any.as_mut()?.downcast_mut::<T>()
-                })
-                .ok()
-            })
-            .flatten()
+    #[track_caller]
+    pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
+        if !self.validate() {
+            return Err(BorrowMutError::Dropped(ValueDroppedError {
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                created_at: self.created_at,
+            }));
+        }
+        self.raw.try_borrow_mut(
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            self.created_at,
+        )
     }
 
     /// Write the value. Panics if the value is no longer valid.
-    pub fn write(&self) -> RefMut<'static, T> {
+    #[track_caller]
+    pub fn write(&self) -> GenerationalRefMut<T> {
         self.try_write().unwrap()
     }
 
     /// Set the value. Panics if the value is no longer valid.
     pub fn set(&self, value: T) {
         self.validate().then(|| {
-            *self.raw.data.borrow_mut() = Some(Box::new(value));
+            *self.raw.0.data.borrow_mut() = Some(Box::new(value));
         });
     }
 
@@ -228,7 +244,8 @@ impl<T: 'static> GenerationalBox<T> {
     pub fn ptr_eq(&self, other: &Self) -> bool {
         #[cfg(any(debug_assertions, feature = "check_generation"))]
         {
-            self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
+            self.raw.0.data.as_ptr() == other.raw.0.data.as_ptr()
+                && self.generation == other.generation
         }
         #[cfg(not(any(debug_assertions, feature = "check_generation")))]
         {
@@ -246,26 +263,37 @@ impl<T> Clone for GenerationalBox<T> {
 }
 
 #[derive(Clone, Copy)]
-struct MemoryLocation {
-    data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
+struct MemoryLocation(&'static MemoryLocationInner);
+
+struct MemoryLocationInner {
+    data: RefCell<Option<Box<dyn std::any::Any>>>,
     #[cfg(any(debug_assertions, feature = "check_generation"))]
-    generation: &'static Cell<u32>,
+    generation: Cell<u32>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrowed_at: RefCell<Vec<&'static std::panic::Location<'static>>>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrowed_mut_at: Cell<Option<&'static std::panic::Location<'static>>>,
 }
 
 impl MemoryLocation {
     #[allow(unused)]
     fn drop(&self) {
-        let old = self.data.borrow_mut().take();
+        let old = self.0.data.borrow_mut().take();
         #[cfg(any(debug_assertions, feature = "check_generation"))]
         if old.is_some() {
             drop(old);
-            let new_generation = self.generation.get() + 1;
-            self.generation.set(new_generation);
+            let new_generation = self.0.generation.get() + 1;
+            self.0.generation.set(new_generation);
         }
     }
 
-    fn replace<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
-        let mut inner_mut = self.data.borrow_mut();
+    fn replace_with_caller<T: 'static>(
+        &mut self,
+        value: T,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        caller: &'static std::panic::Location<'static>,
+    ) -> GenerationalBox<T> {
+        let mut inner_mut = self.0.data.borrow_mut();
 
         let raw = Box::new(value);
         let old = inner_mut.replace(raw);
@@ -273,10 +301,315 @@ impl MemoryLocation {
         GenerationalBox {
             raw: *self,
             #[cfg(any(debug_assertions, feature = "check_generation"))]
-            generation: self.generation.get(),
+            generation: self.0.generation.get(),
+            #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+            created_at: caller,
             _marker: PhantomData,
         }
     }
+
+    #[track_caller]
+    fn try_borrow<T: Any>(
+        &self,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        created_at: &'static std::panic::Location<'static>,
+    ) -> Result<GenerationalRef<T>, BorrowError> {
+        #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+        self.0
+            .borrowed_at
+            .borrow_mut()
+            .push(std::panic::Location::caller());
+        match self.0.data.try_borrow() {
+            Ok(borrow) => match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::<T>()) {
+                Ok(reference) => Ok(GenerationalRef {
+                    inner: reference,
+                    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                    borrow: GenerationalRefBorrowInfo {
+                        borrowed_at: std::panic::Location::caller(),
+                        borrowed_from: self.0,
+                    },
+                }),
+                Err(_) => Err(BorrowError::Dropped(ValueDroppedError {
+                    #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+                    created_at,
+                })),
+            },
+            Err(_) => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError {
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                borrowed_mut_at: self.0.borrowed_mut_at.get().unwrap(),
+            })),
+        }
+    }
+
+    #[track_caller]
+    fn try_borrow_mut<T: Any>(
+        &self,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        created_at: &'static std::panic::Location<'static>,
+    ) -> Result<GenerationalRefMut<T>, BorrowMutError> {
+        #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+        {
+            self.0
+                .borrowed_mut_at
+                .set(Some(std::panic::Location::caller()));
+        }
+        match self.0.data.try_borrow_mut() {
+            Ok(borrow_mut) => {
+                match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::<T>()) {
+                    Ok(reference) => Ok(GenerationalRefMut {
+                        inner: reference,
+                        #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                        borrow: GenerationalRefMutBorrowInfo {
+                            borrowed_from: self.0,
+                        },
+                    }),
+                    Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError {
+                        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+                        created_at,
+                    })),
+                }
+            }
+            Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError {
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                borrowed_at: self.0.borrowed_at.borrow().clone(),
+            })),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+/// An error that can occur when trying to borrow a value.
+pub enum BorrowError {
+    /// The value was dropped.
+    Dropped(ValueDroppedError),
+    /// The value was already borrowed mutably.
+    AlreadyBorrowedMut(AlreadyBorrowedMutError),
+}
+
+impl Display for BorrowError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            BorrowError::Dropped(error) => Display::fmt(error, f),
+            BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f),
+        }
+    }
+}
+
+impl Error for BorrowError {}
+
+#[derive(Debug, Clone)]
+/// An error that can occur when trying to borrow a value mutably.
+pub enum BorrowMutError {
+    /// The value was dropped.
+    Dropped(ValueDroppedError),
+    /// The value was already borrowed.
+    AlreadyBorrowed(AlreadyBorrowedError),
+    /// The value was already borrowed mutably.
+    AlreadyBorrowedMut(AlreadyBorrowedMutError),
+}
+
+impl Display for BorrowMutError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            BorrowMutError::Dropped(error) => Display::fmt(error, f),
+            BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f),
+            BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f),
+        }
+    }
+}
+
+impl Error for BorrowMutError {}
+
+/// An error that can occur when trying to use a value that has been dropped.
+#[derive(Debug, Copy, Clone)]
+pub struct ValueDroppedError {
+    #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+    created_at: &'static std::panic::Location<'static>,
+}
+
+impl Display for ValueDroppedError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("Failed to borrow because the value was dropped.")?;
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        f.write_fmt(format_args!("created_at: {}", self.created_at))?;
+        Ok(())
+    }
+}
+
+impl std::error::Error for ValueDroppedError {}
+
+/// An error that can occur when trying to borrow a value that has already been borrowed mutably.
+#[derive(Debug, Copy, Clone)]
+pub struct AlreadyBorrowedMutError {
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrowed_mut_at: &'static std::panic::Location<'static>,
+}
+
+impl Display for AlreadyBorrowedMutError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("Failed to borrow because the value was already borrowed mutably.")?;
+        #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+        f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?;
+        Ok(())
+    }
+}
+
+impl std::error::Error for AlreadyBorrowedMutError {}
+
+/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably.
+#[derive(Debug, Clone)]
+pub struct AlreadyBorrowedError {
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrowed_at: Vec<&'static std::panic::Location<'static>>,
+}
+
+impl Display for AlreadyBorrowedError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?;
+        #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+        f.write_str("borrowed_at:")?;
+        #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+        for location in self.borrowed_at.iter() {
+            f.write_fmt(format_args!("\t{}", location))?;
+        }
+        Ok(())
+    }
+}
+
+impl std::error::Error for AlreadyBorrowedError {}
+
+/// A reference to a value in a generational box.
+pub struct GenerationalRef<T: 'static> {
+    inner: Ref<'static, T>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrow: GenerationalRefBorrowInfo,
+}
+
+impl<T: 'static> GenerationalRef<T> {
+    /// Map one ref type to another.
+    pub fn map<U, F>(orig: GenerationalRef<T>, f: F) -> GenerationalRef<U>
+    where
+        F: FnOnce(&T) -> &U,
+    {
+        GenerationalRef {
+            inner: Ref::map(orig.inner, f),
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            borrow: GenerationalRefBorrowInfo {
+                borrowed_at: orig.borrow.borrowed_at,
+                borrowed_from: orig.borrow.borrowed_from,
+            },
+        }
+    }
+
+    /// Filter one ref type to another.
+    pub fn filter_map<U, F>(orig: GenerationalRef<T>, f: F) -> Option<GenerationalRef<U>>
+    where
+        F: FnOnce(&T) -> Option<&U>,
+    {
+        let Self {
+            inner,
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            borrow,
+        } = orig;
+        Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef {
+            inner,
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            borrow: GenerationalRefBorrowInfo {
+                borrowed_at: borrow.borrowed_at,
+                borrowed_from: borrow.borrowed_from,
+            },
+        })
+    }
+}
+
+impl<T: 'static> Deref for GenerationalRef<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.inner.deref()
+    }
+}
+
+#[cfg(any(debug_assertions, feature = "debug_borrows"))]
+struct GenerationalRefBorrowInfo {
+    borrowed_at: &'static std::panic::Location<'static>,
+    borrowed_from: &'static MemoryLocationInner,
+}
+
+#[cfg(any(debug_assertions, feature = "debug_borrows"))]
+impl Drop for GenerationalRefBorrowInfo {
+    fn drop(&mut self) {
+        self.borrowed_from
+            .borrowed_at
+            .borrow_mut()
+            .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _));
+    }
+}
+
+/// A mutable reference to a value in a generational box.
+pub struct GenerationalRefMut<T: 'static> {
+    inner: RefMut<'static, T>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrow: GenerationalRefMutBorrowInfo,
+}
+
+impl<T: 'static> GenerationalRefMut<T> {
+    /// Map one ref type to another.
+    pub fn map<U, F>(orig: GenerationalRefMut<T>, f: F) -> GenerationalRefMut<U>
+    where
+        F: FnOnce(&mut T) -> &mut U,
+    {
+        GenerationalRefMut {
+            inner: RefMut::map(orig.inner, f),
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            borrow: orig.borrow,
+        }
+    }
+
+    /// Filter one ref type to another.
+    pub fn filter_map<U, F>(orig: GenerationalRefMut<T>, f: F) -> Option<GenerationalRefMut<U>>
+    where
+        F: FnOnce(&mut T) -> Option<&mut U>,
+    {
+        let Self {
+            inner,
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            borrow,
+        } = orig;
+        RefMut::filter_map(inner, f)
+            .ok()
+            .map(|inner| GenerationalRefMut {
+                inner,
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                borrow,
+            })
+    }
+}
+
+impl<T: 'static> Deref for GenerationalRefMut<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.inner.deref()
+    }
+}
+
+impl<T: 'static> DerefMut for GenerationalRefMut<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.inner.deref_mut()
+    }
+}
+
+#[cfg(any(debug_assertions, feature = "debug_borrows"))]
+struct GenerationalRefMutBorrowInfo {
+    borrowed_from: &'static MemoryLocationInner,
+}
+
+#[cfg(any(debug_assertions, feature = "debug_borrows"))]
+impl Drop for GenerationalRefMutBorrowInfo {
+    fn drop(&mut self) {
+        self.borrowed_from.borrowed_mut_at.take();
+    }
 }
 
 /// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
@@ -305,12 +638,16 @@ impl Store {
         if let Some(location) = self.recycled.borrow_mut().pop() {
             location
         } else {
-            let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None));
-            MemoryLocation {
-                data,
+            let data: &'static MemoryLocationInner = self.bump.alloc(MemoryLocationInner {
+                data: RefCell::new(None),
                 #[cfg(any(debug_assertions, feature = "check_generation"))]
-                generation: self.bump.alloc(Cell::new(0)),
-            }
+                generation: Cell::new(0),
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                borrowed_at: Default::default(),
+                #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+                borrowed_mut_at: Default::default(),
+            });
+            MemoryLocation(data)
         }
     }
 
@@ -331,9 +668,31 @@ pub struct Owner {
 
 impl Owner {
     /// Insert a value into the store. The value will be dropped when the owner is dropped.
+    #[track_caller]
     pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
         let mut location = self.store.claim();
-        let key = location.replace(value);
+        let key = location.replace_with_caller(
+            value,
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            std::panic::Location::caller(),
+        );
+        self.owned.borrow_mut().push(location);
+        key
+    }
+
+    /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped.
+    pub fn insert_with_caller<T: 'static>(
+        &self,
+        value: T,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        caller: &'static std::panic::Location<'static>,
+    ) -> GenerationalBox<T> {
+        let mut location = self.store.claim();
+        let key = location.replace_with_caller(
+            value,
+            #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+            caller,
+        );
         self.owned.borrow_mut().push(location);
         key
     }
@@ -344,7 +703,9 @@ impl Owner {
         GenerationalBox {
             raw: location,
             #[cfg(any(debug_assertions, feature = "check_generation"))]
-            generation: location.generation.get(),
+            generation: location.0.generation.get(),
+            #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+            created_at: std::panic::Location::caller(),
             _marker: PhantomData,
         }
     }

+ 1 - 1
packages/signals/src/effect.rs

@@ -100,7 +100,7 @@ impl Effect {
 
     /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead.
     pub fn try_run(&self) {
-        if let Some(mut callback) = self.callback.try_write() {
+        if let Ok(mut callback) = self.callback.try_write() {
             {
                 self.effect_stack.effects.write().push(*self);
             }

+ 17 - 17
packages/signals/src/impls.rs

@@ -1,7 +1,7 @@
 use crate::rt::CopyValue;
 use crate::signal::{ReadOnlySignal, Signal, Write};
-
-use std::cell::{Ref, RefMut};
+use generational_box::GenerationalRef;
+use generational_box::GenerationalRefMut;
 
 use std::{
     fmt::{Debug, Display},
@@ -38,8 +38,8 @@ macro_rules! read_impls {
 
         impl<T: 'static> $ty<Vec<T>> {
             /// Read a value from the inner vector.
-            pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
-                Ref::filter_map(self.read(), |v| v.get(index)).ok()
+            pub fn get(&self, index: usize) -> Option<GenerationalRef<T>> {
+                GenerationalRef::filter_map(self.read(), |v| v.get(index))
             }
         }
 
@@ -52,9 +52,9 @@ macro_rules! read_impls {
                 self.with(|v| v.clone()).unwrap()
             }
 
-            /// Attemps to read the inner value of the Option.
-            pub fn as_ref(&self) -> Option<Ref<'_, T>> {
-                Ref::filter_map(self.read(), |v| v.as_ref()).ok()
+            /// Attempts to read the inner value of the Option.
+            pub fn as_ref(&self) -> Option<GenerationalRef<T>> {
+                GenerationalRef::filter_map(self.read(), |v| v.as_ref())
             }
         }
     };
@@ -182,19 +182,19 @@ macro_rules! write_impls {
             }
 
             /// Gets the value out of the Option, or inserts the given value if the Option is empty.
-            pub fn get_or_insert(&self, default: T) -> Ref<'_, T> {
+            pub fn get_or_insert(&self, default: T) -> GenerationalRef<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.
-            pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> {
+            pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> GenerationalRef<T> {
                 let borrow = self.read();
                 if borrow.is_none() {
                     drop(borrow);
                     self.with_mut(|v| *v = Some(default()));
-                    Ref::map(self.read(), |v| v.as_ref().unwrap())
+                    GenerationalRef::map(self.read(), |v| v.as_ref().unwrap())
                 } else {
-                    Ref::map(borrow, |v| v.as_ref().unwrap())
+                    GenerationalRef::map(borrow, |v| v.as_ref().unwrap())
                 }
             }
         }
@@ -238,15 +238,15 @@ impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
 
 impl<T: 'static> CopyValue<Vec<T>> {
     /// Write to an element in the inner vector.
-    pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
-        RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
+    pub fn get_mut(&self, index: usize) -> Option<GenerationalRefMut<T>> {
+        GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index))
     }
 }
 
 impl<T: 'static> CopyValue<Option<T>> {
     /// Deref the inner value mutably.
-    pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
-        RefMut::filter_map(self.write(), |v| v.as_mut()).ok()
+    pub fn as_mut(&self) -> Option<GenerationalRefMut<T>> {
+        GenerationalRefMut::filter_map(self.write(), |v| v.as_mut())
     }
 }
 
@@ -281,14 +281,14 @@ impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
 
 impl<T: 'static> Signal<Vec<T>> {
     /// Returns a reference to an element or `None` if out of bounds.
-    pub fn get_mut(&self, index: usize) -> Option<Write<'_, T, Vec<T>>> {
+    pub fn get_mut(&self, index: usize) -> Option<Write<T, Vec<T>>> {
         Write::filter_map(self.write(), |v| v.get_mut(index))
     }
 }
 
 impl<T: 'static> Signal<Option<T>> {
     /// Returns a reference to an element or `None` if out of bounds.
-    pub fn as_mut(&self) -> Option<Write<'_, T, Option<T>>> {
+    pub fn as_mut(&self) -> Option<Write<T, Option<T>>> {
         Write::filter_map(self.write(), |v| v.as_mut())
     }
 }

+ 29 - 8
packages/signals/src/rt.rs

@@ -1,5 +1,3 @@
-use std::cell::{Ref, RefMut};
-
 use std::mem::MaybeUninit;
 use std::ops::Deref;
 use std::rc::Rc;
@@ -7,7 +5,9 @@ use std::rc::Rc;
 use dioxus_core::prelude::*;
 use dioxus_core::ScopeId;
 
-use generational_box::{GenerationalBox, Owner, Store};
+use generational_box::{
+    BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store,
+};
 
 use crate::Effect;
 
@@ -83,6 +83,7 @@ impl<T: 'static> CopyValue<T> {
     /// Create a new CopyValue. The value will be stored in the current component.
     ///
     /// Once the component this value is created in is dropped, the value will be dropped.
+    #[track_caller]
     pub fn new(value: T) -> Self {
         let owner = current_owner();
 
@@ -92,6 +93,22 @@ impl<T: 'static> CopyValue<T> {
         }
     }
 
+    pub(crate) fn new_with_caller(
+        value: T,
+        #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>,
+    ) -> Self {
+        let owner = current_owner();
+
+        Self {
+            value: owner.insert_with_caller(
+                value,
+                #[cfg(debug_assertions)]
+                caller,
+            ),
+            origin_scope: current_scope_id().expect("in a virtual dom"),
+        }
+    }
+
     /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped.
     pub fn new_in_scope(value: T, scope: ScopeId) -> Self {
         let owner = owner_in_scope(scope);
@@ -117,22 +134,26 @@ impl<T: 'static> CopyValue<T> {
     }
 
     /// Try to read the value. If the value has been dropped, this will return None.
-    pub fn try_read(&self) -> Option<Ref<'_, T>> {
+    #[track_caller]
+    pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
         self.value.try_read()
     }
 
     /// Read the value. If the value has been dropped, this will panic.
-    pub fn read(&self) -> Ref<'static, T> {
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<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<'static, T>> {
+    #[track_caller]
+    pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
         self.value.try_write()
     }
 
     /// Write the value. If the value has been dropped, this will panic.
-    pub fn write(&self) -> RefMut<'static, T> {
+    #[track_caller]
+    pub fn write(&self) -> GenerationalRefMut<T> {
         self.value.write()
     }
 
@@ -168,7 +189,7 @@ impl<T: 'static> PartialEq for CopyValue<T> {
 }
 
 impl<T> Deref for CopyValue<T> {
-    type Target = dyn Fn() -> Ref<'static, T>;
+    type Target = dyn Fn() -> GenerationalRef<T>;
 
     fn deref(&self) -> &Self::Target {
         // https://github.com/dtolnay/case-studies/tree/master/callable-types

+ 58 - 18
packages/signals/src/signal.rs

@@ -1,5 +1,5 @@
 use std::{
-    cell::{Ref, RefCell, RefMut},
+    cell::RefCell,
     mem::MaybeUninit,
     ops::{Deref, DerefMut},
     rc::Rc,
@@ -10,6 +10,7 @@ use dioxus_core::{
     prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
     ScopeId, ScopeState,
 };
+use generational_box::{GenerationalRef, GenerationalRefMut};
 
 use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
 
@@ -44,9 +45,19 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
 ///     }
 /// }
 /// ```
+#[track_caller]
 #[must_use]
 pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
-    *cx.use_hook(|| Signal::new(f()))
+    #[cfg(debug_assertions)]
+    let caller = std::panic::Location::caller();
+
+    *cx.use_hook(|| {
+        Signal::new_with_caller(
+            f(),
+            #[cfg(debug_assertions)]
+            caller,
+        )
+    })
 }
 
 #[derive(Clone)]
@@ -138,6 +149,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign
 
 impl<T: 'static> Signal<T> {
     /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
+    #[track_caller]
     pub fn new(value: T) -> Self {
         Self {
             inner: CopyValue::new(SignalData {
@@ -150,6 +162,26 @@ impl<T: 'static> Signal<T> {
         }
     }
 
+    /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
+    fn new_with_caller(
+        value: T,
+        #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>,
+    ) -> Self {
+        Self {
+            inner: CopyValue::new_with_caller(
+                SignalData {
+                    subscribers: Default::default(),
+                    effect_subscribers: Default::default(),
+                    update_any: schedule_update_any().expect("in a virtual dom"),
+                    value,
+                    effect_stack: get_effect_stack(),
+                },
+                #[cfg(debug_assertions)]
+                caller,
+            ),
+        }
+    }
+
     /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope.
     pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
         Self {
@@ -173,7 +205,8 @@ impl<T: 'static> Signal<T> {
 
     /// Get the current value of the signal. This will subscribe the current scope to the signal.
     /// If the signal has been dropped, this will panic.
-    pub fn read(&self) -> Ref<T> {
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<T> {
         let inner = self.inner.read();
         if let Some(effect) = inner.effect_stack.current() {
             let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
@@ -197,14 +230,15 @@ impl<T: 'static> Signal<T> {
                 }
             }
         }
-        Ref::map(inner, |v| &v.value)
+        GenerationalRef::map(inner, |v| &v.value)
     }
 
     /// Get a mutable reference to the signal's value.
     /// If the signal has been dropped, this will panic.
-    pub fn write(&self) -> Write<'_, T> {
+    #[track_caller]
+    pub fn write(&self) -> Write<T> {
         let inner = self.inner.write();
-        let borrow = RefMut::map(inner, |v| &mut v.value);
+        let borrow = GenerationalRefMut::map(inner, |v| &mut v.value);
         Write {
             write: borrow,
             signal: SignalSubscriberDrop { signal: *self },
@@ -240,12 +274,14 @@ impl<T: 'static> Signal<T> {
     }
 
     /// Set the value of the signal. This will trigger an update on all subscribers.
+    #[track_caller]
     pub fn set(&self, value: T) {
         *self.write() = value;
     }
 
     /// Run a closure with a reference to the signal's value.
     /// If the signal has been dropped, this will panic.
+    #[track_caller]
     pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
         let write = self.read();
         f(&*write)
@@ -253,6 +289,7 @@ impl<T: 'static> Signal<T> {
 
     /// Run a closure with a mutable reference to the signal's value.
     /// If the signal has been dropped, this will panic.
+    #[track_caller]
     pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
         let mut write = self.write();
         f(&mut *write)
@@ -262,6 +299,7 @@ impl<T: 'static> Signal<T> {
 impl<T: Clone + 'static> Signal<T> {
     /// Get the current value of the signal. This will subscribe the current scope to the signal.
     /// If the signal has been dropped, this will panic.
+    #[track_caller]
     pub fn value(&self) -> T {
         self.read().clone()
     }
@@ -281,7 +319,7 @@ impl<T: 'static> PartialEq for Signal<T> {
 }
 
 impl<T> Deref for Signal<T> {
-    type Target = dyn Fn() -> Ref<'static, T>;
+    type Target = dyn Fn() -> GenerationalRef<T>;
 
     fn deref(&self) -> &Self::Target {
         // https://github.com/dtolnay/case-studies/tree/master/callable-types
@@ -324,17 +362,17 @@ impl<T: 'static> Drop for SignalSubscriberDrop<T> {
 }
 
 /// A mutable reference to a signal's value.
-pub struct Write<'a, T: 'static, I: 'static = T> {
-    write: RefMut<'a, T>,
+pub struct Write<T: 'static, I: 'static = T> {
+    write: GenerationalRefMut<T>,
     signal: SignalSubscriberDrop<I>,
 }
 
-impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
+impl<T: 'static, I: 'static> Write<T, I> {
     /// Map the mutable reference to the signal's value to a new type.
-    pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
+    pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<O, I> {
         let Self { write, signal } = myself;
         Write {
-            write: RefMut::map(write, f),
+            write: GenerationalRefMut::map(write, f),
             signal,
         }
     }
@@ -343,14 +381,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
     pub fn filter_map<O>(
         myself: Self,
         f: impl FnOnce(&mut T) -> Option<&mut O>,
-    ) -> Option<Write<'a, O, I>> {
+    ) -> Option<Write<O, I>> {
         let Self { write, signal } = myself;
-        let write = RefMut::filter_map(write, f).ok();
+        let write = GenerationalRefMut::filter_map(write, f);
         write.map(|write| Write { write, signal })
     }
 }
 
-impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
+impl<T: 'static, I: 'static> Deref for Write<T, I> {
     type Target = T;
 
     fn deref(&self) -> &Self::Target {
@@ -358,7 +396,7 @@ impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
     }
 }
 
-impl<T, I> DerefMut for Write<'_, T, I> {
+impl<T, I> DerefMut for Write<T, I> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.write
     }
@@ -381,11 +419,13 @@ impl<T: 'static> ReadOnlySignal<T> {
     }
 
     /// Get the current value of the signal. This will subscribe the current scope to the signal.
-    pub fn read(&self) -> Ref<T> {
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<T> {
         self.inner.read()
     }
 
     /// Run a closure with a reference to the signal's value.
+    #[track_caller]
     pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
         self.inner.with(f)
     }
@@ -405,7 +445,7 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
 }
 
 impl<T> Deref for ReadOnlySignal<T> {
-    type Target = dyn Fn() -> Ref<'static, T>;
+    type Target = dyn Fn() -> GenerationalRef<T>;
 
     fn deref(&self) -> &Self::Target {
         // https://github.com/dtolnay/case-studies/tree/master/callable-types