leptos_reactive 0.1.3

Reactive system for the Leptos web framework.
Documentation
#![forbid(unsafe_code)]
use crate::{
    debug_warn,
    runtime::{with_runtime, RuntimeId},
    Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal,
};
use cfg_if::cfg_if;
use futures::Stream;
use std::{fmt::Debug, marker::PhantomData};
use thiserror::Error;

/// Creates a signal, the basic reactive primitive.
///
/// A signal is a piece of data that may change over time,
/// and notifies other code when it has changed. This is the
/// core primitive of Leptos’s reactive system.
///
/// Takes a reactive [Scope] and the initial value as arguments,
/// and returns a tuple containing a [ReadSignal] and a [WriteSignal],
/// each of which can be called as a function.
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // âś… calling the getter clones and returns the value
/// assert_eq!(count(), 0);
///
/// // âś… calling the setter sets the value
/// set_count(1);
/// assert_eq!(count(), 1);
///
/// // ❌ don't try to call the getter within the setter
/// // set_count(count() + 1);
///
/// // âś… instead, use .update() to mutate the value in place
/// set_count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count(), 2);
///
/// // âś… you can create "derived signals" with the same Fn() -> T interface
/// let double_count = move || count() * 2; // signals are `Copy` so you can `move` them anywhere
/// set_count(0);
/// assert_eq!(double_count(), 0);
/// set_count(1);
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
/// ```
#[cfg_attr(
    debug_assertions,
    instrument(
        level = "trace",
        skip_all,
        fields(
            scope = ?cx.id,
            ty = %std::any::type_name::<T>()
        )
    )
)]
#[track_caller]
pub fn create_signal<T>(cx: Scope, value: T) -> (ReadSignal<T>, WriteSignal<T>) {
    let s = cx.runtime.create_signal(value);
    cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.0.id)));
    s
}

/// Creates a signal that always contains the most recent value emitted by a
/// [Stream](futures::stream::Stream).
/// If the stream has not yet emitted a value since the signal was created, the signal's
/// value will be `None`.
///
/// **Note**: If used on the server side during server rendering, this will return `None`
/// immediately and not begin driving the stream.
#[cfg_attr(
    debug_assertions,
    instrument(
        level = "trace",
        skip_all,
        fields(
            scope = ?cx.id,
        )
    )
)]
pub fn create_signal_from_stream<T>(
    cx: Scope,
    #[allow(unused_mut)] // allowed because needed for SSR
    mut stream: impl Stream<Item = T> + Unpin + 'static,
) -> ReadSignal<Option<T>> {
    cfg_if! {
        if #[cfg(feature = "ssr")] {
            _ = stream;
            let (read, _) = create_signal(cx, None);
            read
        } else {
            use crate::spawn_local;
            use futures::StreamExt;

            let (read, write) = create_signal(cx, None);
            spawn_local(async move {
                while let Some(value) = stream.next().await {
                    write.set(Some(value));
                }
            });
            read
        }
    }
}

/// The getter for a reactive signal.
///
/// A signal is a piece of data that may change over time,
/// and notifies other code when it has changed. This is the
/// core primitive of Leptos’s reactive system.
///
/// Calling [ReadSignal::get] within an effect will cause that effect
/// to subscribe to the signal, and to re-run whenever the value of
/// the signal changes.
///
/// `ReadSignal` implements [Fn], so that `value()` and `value.get()` are identical.
///
/// `ReadSignal` is also [Copy] and `'static`, so it can very easily moved into closures
/// or copied structs.
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // âś… calling the getter clones and returns the value
/// assert_eq!(count(), 0);
///
/// // âś… calling the setter sets the value
/// set_count(1);
/// assert_eq!(count(), 1);
///
/// // ❌ don't try to call the getter within the setter
/// // set_count(count() + 1);
///
/// // âś… instead, use .update() to mutate the value in place
/// set_count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count(), 2);
///
/// // âś… you can create "derived signals" with the same Fn() -> T interface
/// let double_count = move || count() * 2; // signals are `Copy` so you can `move` them anywhere
/// set_count(0);
/// assert_eq!(double_count(), 0);
/// set_count(1);
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
/// ```
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ReadSignal<T>
where
    T: 'static,
{
    pub(crate) runtime: RuntimeId,
    pub(crate) id: SignalId,
    pub(crate) ty: PhantomData<T>,
    #[cfg(debug_assertions)]
    pub(crate) defined_at: &'static std::panic::Location<'static>,
}

impl<T> UntrackedGettableSignal<T> for ReadSignal<T> {
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "ReadSignal::get_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn get_untracked(&self) -> T
    where
        T: Clone,
    {
        self.with_no_subscription(|v| v.clone())
    }

    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "ReadSignal::with_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
        self.with_no_subscription(f)
    }
}

impl<T> ReadSignal<T>
where
    T: 'static,
{
    /// Applies a function to the current value of the signal, and subscribes
    /// the running effect to this signal.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let (name, set_name) = create_signal(cx, "Alice".to_string());
    ///
    /// // ❌ unnecessarily clones the string
    /// let first_char = move || name().chars().next().unwrap();
    /// assert_eq!(first_char(), 'A');
    ///
    /// // âś… gets the first char without cloning the `String`
    /// let first_char = move || name.with(|n| n.chars().next().unwrap());
    /// assert_eq!(first_char(), 'A');
    /// set_name("Bob".to_string());
    /// assert_eq!(first_char(), 'B');
    /// });
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "ReadSignal::with()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
        self.id.with(self.runtime, f)
    }

    pub(crate) fn with_no_subscription<U>(&self, f: impl FnOnce(&T) -> U) -> U {
        self.id.with_no_subscription(self.runtime, f)
    }

    #[cfg(feature = "hydrate")]
    pub(crate) fn subscribe(&self) {
        _ = with_runtime(self.runtime, |runtime| self.id.subscribe(runtime))
    }

    /// Clones and returns the current value of the signal, and subscribes
    /// the running effect to this signal.
    ///
    /// If you want to get the value without cloning it, use [ReadSignal::with].
    /// (`value.get()` is equivalent to `value.with(T::clone)`.)
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let (count, set_count) = create_signal(cx, 0);
    ///
    /// // calling the getter clones and returns the value
    /// assert_eq!(count(), 0);
    /// });
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "ReadSignal::get()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.id.with(self.runtime, T::clone)
    }

    /// Applies the function to the current Signal, if it exists, and subscribes
    /// the running effect.
    pub(crate) fn try_with<U>(&self, f: impl FnOnce(&T) -> U) -> Result<U, SignalError> {
        match with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f)) {
            Ok(Ok(v)) => Ok(v),
            Ok(Err(e)) => Err(e),
            Err(_) => Err(SignalError::RuntimeDisposed),
        }
    }

    /// Generates a [Stream](futures::stream::Stream) that emits the new value of the signal
    /// whenever it changes.
    pub fn to_stream(&self) -> impl Stream<Item = T>
    where
        T: Clone,
    {
        let (tx, rx) = futures::channel::mpsc::unbounded();
        let id = self.id;
        let runtime = self.runtime;
        // TODO: because it's not attached to a scope, this effect will leak if the scope is disposed
        runtime.create_effect(move |_| {
            _ = tx.unbounded_send(id.with(runtime, T::clone));
        });
        rx
    }
}

impl<T> Clone for ReadSignal<T> {
    fn clone(&self) -> Self {
        Self {
            runtime: self.runtime,
            id: self.id,
            ty: PhantomData,
            #[cfg(debug_assertions)]
            defined_at: self.defined_at,
        }
    }
}

impl<T> Copy for ReadSignal<T> {}

#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for ReadSignal<T>
where
    T: Clone,
{
    type Output = T;

    extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
        self.get()
    }
}

#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for ReadSignal<T>
where
    T: Clone,
{
    extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
        self.get()
    }
}

#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for ReadSignal<T>
where
    T: Clone,
{
    extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
        self.get()
    }
}

/// The setter for a reactive signal.
///
/// A signal is a piece of data that may change over time,
/// and notifies other code when it has changed. This is the
/// core primitive of Leptos’s reactive system.
///
/// Calling [WriteSignal::update] will mutate the signal’s value in place,
/// and notify all subscribers that the signal’s value has changed.
///
/// `ReadSignal` implements [Fn], such that `set_value(new_value)` is equivalent to
/// `set_value.update(|value| *value = new_value)`.
///
/// `WriteSignal` is [Copy] and `'static`, so it can very easily moved into closures
/// or copied structs.
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // âś… calling the setter sets the value
/// set_count(1);
/// assert_eq!(count(), 1);
///
/// // ❌ don't try to call the getter within the setter
/// // set_count(count() + 1);
///
/// // âś… instead, use .update() to mutate the value in place
/// set_count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count(), 2);
/// # }).dispose();
/// #
/// ```
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct WriteSignal<T>
where
    T: 'static,
{
    pub(crate) runtime: RuntimeId,
    pub(crate) id: SignalId,
    pub(crate) ty: PhantomData<T>,
    #[cfg(debug_assertions)]
    pub(crate) defined_at: &'static std::panic::Location<'static>,
}

impl<T> UntrackedSettableSignal<T> for WriteSignal<T>
where
    T: 'static,
{
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "WriteSignal::set_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn set_untracked(&self, new_value: T) {
        self.id
            .update_with_no_effect(self.runtime, |v| *v = new_value);
    }

    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "WriteSignal::updated_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn update_untracked(&self, f: impl FnOnce(&mut T)) {
        self.id.update_with_no_effect(self.runtime, f);
    }

    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "WriteSignal::update_returning_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn update_returning_untracked<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
        self.id.update_with_no_effect(self.runtime, f)
    }
}

impl<T> WriteSignal<T>
where
    T: 'static,
{
    /// Applies a function to the current value to mutate it in place
    /// and notifies subscribers that the signal has changed.
    ///
    /// **Note:** `update()` does not auto-memoize, i.e., it will notify subscribers
    /// even if the value has not actually changed.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let (count, set_count) = create_signal(cx, 0);
    ///
    /// // notifies subscribers
    /// set_count.update(|n| *n = 1); // it's easier just to call set_count(1), though!
    /// assert_eq!(count(), 1);
    ///
    /// // you can include arbitrary logic in this update function
    /// // also notifies subscribers, even though the value hasn't changed
    /// set_count.update(|n| if *n > 3 { *n += 1 });
    /// assert_eq!(count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            name = "WriteSignal::update()",
            level = "trace",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn update(&self, f: impl FnOnce(&mut T)) {
        self.id.update(self.runtime, f);
    }

    /// Applies a function to the current value to mutate it in place
    /// and notifies subscribers that the signal has changed.
    /// Forwards the return value of the closure if the closure was called
    ///
    /// **Note:** `update()` does not auto-memoize, i.e., it will notify subscribers
    /// even if the value has not actually changed.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let (count, set_count) = create_signal(cx, 0);
    ///
    /// // notifies subscribers
    /// let value = set_count.update_returning(|n| { *n = 1; *n * 10 });
    /// assert_eq!(value, Some(10));
    /// assert_eq!(count(), 1);
    ///
    /// let value = set_count.update_returning(|n| { *n += 1; *n * 10 });
    /// assert_eq!(value, Some(20));
    /// assert_eq!(count(), 2);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "WriteSignal::update_returning()"
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn update_returning<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
        self.id.update(self.runtime, f)
    }

    /// Sets the signal’s value and notifies subscribers.
    ///
    /// **Note:** `set()` does not auto-memoize, i.e., it will notify subscribers
    /// even if the value has not actually changed.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let (count, set_count) = create_signal(cx, 0);
    ///
    /// // notifies subscribers
    /// set_count.update(|n| *n = 1); // it's easier just to call set_count(1), though!
    /// assert_eq!(count(), 1);
    ///
    /// // you can include arbitrary logic in this update function
    /// // also notifies subscribers, even though the value hasn't changed
    /// set_count.update(|n| if *n > 3 { *n += 1 });
    /// assert_eq!(count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "WriteSignal::set()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn set(&self, new_value: T) {
        self.id.update(self.runtime, |n| *n = new_value);
    }
}

impl<T> Clone for WriteSignal<T> {
    fn clone(&self) -> Self {
        Self {
            runtime: self.runtime,
            id: self.id,
            ty: PhantomData,
            #[cfg(debug_assertions)]
            defined_at: self.defined_at,
        }
    }
}

impl<T> Copy for WriteSignal<T> {}

#[cfg(not(feature = "stable"))]
impl<T> FnOnce<(T,)> for WriteSignal<T>
where
    T: 'static,
{
    type Output = ();

    extern "rust-call" fn call_once(self, args: (T,)) -> Self::Output {
        self.update(move |n| *n = args.0)
    }
}

#[cfg(not(feature = "stable"))]
impl<T> FnMut<(T,)> for WriteSignal<T>
where
    T: 'static,
{
    extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
        self.update(move |n| *n = args.0)
    }
}

#[cfg(not(feature = "stable"))]
impl<T> Fn<(T,)> for WriteSignal<T>
where
    T: 'static,
{
    extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
        self.update(move |n| *n = args.0)
    }
}

/// Creates a reactive signal with the getter and setter unified in one value.
/// You may prefer this style, or it may be easier to pass around in a context
/// or as a function argument.
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let count = create_rw_signal(cx, 0);
///
/// // âś… set the value
/// count.set(1);
/// assert_eq!(count(), 1);
///
/// // ❌ don't try to call the getter within the setter
/// // count.set(count.get() + 1);
///
/// // âś… instead, use .update() to mutate the value in place
/// count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count(), 2);
/// # }).dispose();
/// #
/// ```
#[cfg_attr(
    debug_assertions,
    instrument(
        level = "trace",
        skip_all,
        fields(
            ty = %std::any::type_name::<T>()
        )
    )
)]
pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
    let s = cx.runtime.create_rw_signal(value);
    cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.id)));
    s
}

/// A signal that combines the getter and setter into one value, rather than
/// separating them into a [ReadSignal] and a [WriteSignal]. You may prefer this
/// its style, or it may be easier to pass around in a context or as a function argument.
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let count = create_rw_signal(cx, 0);
///
/// // âś… set the value
/// count.set(1);
/// assert_eq!(count(), 1);
///
/// // ❌ don't try to call the getter within the setter
/// // count.set(count.get() + 1);
///
/// // âś… instead, use .update() to mutate the value in place
/// count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count(), 2);
/// # }).dispose();
/// #
/// ```
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct RwSignal<T>
where
    T: 'static,
{
    pub(crate) runtime: RuntimeId,
    pub(crate) id: SignalId,
    pub(crate) ty: PhantomData<T>,
    #[cfg(debug_assertions)]
    pub(crate) defined_at: &'static std::panic::Location<'static>,
}

impl<T> Clone for RwSignal<T> {
    fn clone(&self) -> Self {
        Self {
            runtime: self.runtime,
            id: self.id,
            ty: self.ty,
            #[cfg(debug_assertions)]
            defined_at: self.defined_at,
        }
    }
}

impl<T> Copy for RwSignal<T> {}

impl<T> UntrackedGettableSignal<T> for RwSignal<T> {
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::get_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn get_untracked(&self) -> T
    where
        T: Clone,
    {
        self.id
            .with_no_subscription(self.runtime, |v: &T| v.clone())
    }

    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::with_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
        self.id.with_no_subscription(self.runtime, f)
    }
}

impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::set_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn set_untracked(&self, new_value: T) {
        self.id
            .update_with_no_effect(self.runtime, |v| *v = new_value);
    }

    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::update_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn update_untracked(&self, f: impl FnOnce(&mut T)) {
        self.id.update_with_no_effect(self.runtime, f);
    }

    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::update_returning_untracked()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    fn update_returning_untracked<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
        self.id.update_with_no_effect(self.runtime, f)
    }
}

impl<T> RwSignal<T>
where
    T: 'static,
{
    /// Applies a function to the current value of the signal, and subscribes
    /// the running effect to this signal.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let name = create_rw_signal(cx, "Alice".to_string());
    ///
    /// // ❌ unnecessarily clones the string
    /// let first_char = move || name().chars().next().unwrap();
    /// assert_eq!(first_char(), 'A');
    ///
    /// // âś… gets the first char without cloning the `String`
    /// let first_char = move || name.with(|n| n.chars().next().unwrap());
    /// assert_eq!(first_char(), 'A');
    /// name.set("Bob".to_string());
    /// assert_eq!(first_char(), 'B');
    /// # }).dispose();
    /// #
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::with()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
        self.id.with(self.runtime, f)
    }

    /// Clones and returns the current value of the signal, and subscribes
    /// the running effect to this signal.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    ///
    /// assert_eq!(count.get(), 0);
    ///
    /// // count() is shorthand for count.get()
    /// assert_eq!(count(), 0);
    /// # }).dispose();
    /// #
    /// ```   
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::get()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.id.with(self.runtime, T::clone)
    }

    /// Applies a function to the current value to mutate it in place
    /// and notifies subscribers that the signal has changed.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    ///
    /// // notifies subscribers
    /// count.update(|n| *n = 1); // it's easier just to call set_count(1), though!
    /// assert_eq!(count(), 1);
    ///
    /// // you can include arbitrary logic in this update function
    /// // also notifies subscribers, even though the value hasn't changed
    /// count.update(|n| if *n > 3 { *n += 1 });
    /// assert_eq!(count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::update()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn update(&self, f: impl FnOnce(&mut T)) {
        self.id.update(self.runtime, f);
    }

    /// Applies a function to the current value to mutate it in place
    /// and notifies subscribers that the signal has changed.
    /// Forwards the return value of the closure if the closure was called
    ///
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    ///
    /// // notifies subscribers
    /// let value = count.update_returning(|n| { *n = 1; *n * 10 });
    /// assert_eq!(value, Some(10));
    /// assert_eq!(count(), 1);
    ///
    /// let value = count.update_returning(|n| { *n += 1; *n * 10 });
    /// assert_eq!(value, Some(20));
    /// assert_eq!(count(), 2);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::update_returning()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn update_returning<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
        self.id.update(self.runtime, f)
    }

    /// Sets the signal’s value and notifies subscribers.
    ///
    /// **Note:** `set()` does not auto-memoize, i.e., it will notify subscribers
    /// even if the value has not actually changed.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    ///
    /// assert_eq!(count(), 0);
    /// count.set(1);
    /// assert_eq!(count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::set()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn set(&self, value: T) {
        self.id.update(self.runtime, |n| *n = value);
    }

    /// Returns a read-only handle to the signal.
    ///
    /// Useful if you're trying to give read access to another component but ensure that it can't write
    /// to the signal and cause other parts of the DOM to update.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    /// let read_count = count.read_only();
    /// assert_eq!(count(), 0);
    /// assert_eq!(read_count(), 0);
    /// count.set(1);
    /// assert_eq!(count(), 1);
    /// assert_eq!(read_count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::read_only()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    #[track_caller]
    pub fn read_only(&self) -> ReadSignal<T> {
        ReadSignal {
            runtime: self.runtime,
            id: self.id,
            ty: PhantomData,
            #[cfg(debug_assertions)]
            defined_at: std::panic::Location::caller(),
        }
    }

    /// Returns a write-only handle to the signal.
    ///
    /// Useful if you're trying to give write access to another component, or split an
    /// `RwSignal` into a [ReadSignal] and a [WriteSignal].
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    /// let set_count = count.write_only();
    /// assert_eq!(count(), 0);
    /// set_count(1);
    /// assert_eq!(count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::write_only()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    #[track_caller]
    pub fn write_only(&self) -> WriteSignal<T> {
        WriteSignal {
            runtime: self.runtime,
            id: self.id,
            ty: PhantomData,
            #[cfg(debug_assertions)]
            defined_at: std::panic::Location::caller(),
        }
    }

    /// Splits an `RwSignal` into its getter and setter.
    /// ```
    /// # use leptos_reactive::*;
    /// # create_scope(create_runtime(), |cx| {
    /// let count = create_rw_signal(cx, 0);
    /// let (get_count, set_count) = count.split();
    /// assert_eq!(count(), 0);
    /// assert_eq!(get_count(), 0);
    /// set_count(1);
    /// assert_eq!(count(), 1);
    /// assert_eq!(get_count(), 1);
    /// # }).dispose();
    /// ```
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::split()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    #[track_caller]
    pub fn split(&self) -> (ReadSignal<T>, WriteSignal<T>) {
        (
            ReadSignal {
                runtime: self.runtime,
                id: self.id,
                ty: PhantomData,
                #[cfg(debug_assertions)]
                defined_at: std::panic::Location::caller(),
            },
            WriteSignal {
                runtime: self.runtime,
                id: self.id,
                ty: PhantomData,
                #[cfg(debug_assertions)]
                defined_at: std::panic::Location::caller(),
            },
        )
    }

    /// Generates a [Stream](futures::stream::Stream) that emits the new value of the signal
    /// whenever it changes.
    #[cfg_attr(
        debug_assertions,
        instrument(
            level = "trace",
            name = "RwSignal::to_stream()",
            skip_all,
            fields(
                id = ?self.id,
                defined_at = %self.defined_at,
                ty = %std::any::type_name::<T>()
            )
        )
    )]
    pub fn to_stream(&self) -> impl Stream<Item = T>
    where
        T: Clone,
    {
        self.read_only().to_stream()
    }
}

#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for RwSignal<T>
where
    T: Clone,
{
    type Output = T;

    extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
        self.get()
    }
}

#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for RwSignal<T>
where
    T: Clone,
{
    extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
        self.get()
    }
}

#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for RwSignal<T>
where
    T: Clone,
{
    extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
        self.get()
    }
}

// Internals
slotmap::new_key_type! {
    /// Unique ID assigned to a signal.
    pub struct SignalId;
}

#[derive(Debug, Error)]
pub(crate) enum SignalError {
    #[error("tried to access a signal in a runtime that had been disposed")]
    RuntimeDisposed,
    #[error("tried to access a signal that had been disposed")]
    Disposed,
    #[error("error casting signal to type {0}")]
    Type(&'static str),
}

impl SignalId {
    pub(crate) fn subscribe(&self, runtime: &Runtime) {
        // add subscriber
        if let Some(observer) = runtime.observer.get() {
            // add this observer to the signal's dependencies (to allow notification)
            let mut subs = runtime.signal_subscribers.borrow_mut();
            if let Some(subs) = subs.entry(*self) {
                subs.or_default().borrow_mut().insert(observer);
            }

            // add this signal to the effect's sources (to allow cleanup)
            let mut effect_sources = runtime.effect_sources.borrow_mut();
            if let Some(effect_sources) = effect_sources.entry(observer) {
                let sources = effect_sources.or_default();
                sources.borrow_mut().insert(*self);
            }
        }
    }

    pub(crate) fn try_with_no_subscription<T, U>(
        &self,
        runtime: &Runtime,
        f: impl FnOnce(&T) -> U,
    ) -> Result<U, SignalError>
    where
        T: 'static,
    {
        // get the value
        let value = {
            let signals = runtime.signals.borrow();
            match signals.get(*self).cloned().ok_or(SignalError::Disposed) {
                Ok(s) => Ok(s),
                Err(e) => {
                    debug_warn!("[Signal::try_with] {e}");
                    Err(e)
                }
            }
        }?;
        let value = value.try_borrow().unwrap_or_else(|e| {
            debug_warn!(
                "Signal::try_with_no_subscription failed on Signal<{}>. It seems you're trying to read the value of a signal within an effect caused by updating the signal.",
                std::any::type_name::<T>()
            );
            panic!("{e}");
        });
        let value = value
            .downcast_ref::<T>()
            .ok_or_else(|| SignalError::Type(std::any::type_name::<T>()))?;
        Ok(f(value))
    }

    pub(crate) fn try_with<T, U>(
        &self,
        runtime: &Runtime,
        f: impl FnOnce(&T) -> U,
    ) -> Result<U, SignalError>
    where
        T: 'static,
    {
        self.subscribe(runtime);

        self.try_with_no_subscription(runtime, f)
    }

    pub(crate) fn with_no_subscription<T, U>(
        &self,
        runtime: RuntimeId,
        f: impl FnOnce(&T) -> U,
    ) -> U
    where
        T: 'static,
    {
        with_runtime(runtime, |runtime| {
            self.try_with_no_subscription(runtime, f).unwrap()
        })
        .expect("tried to access a signal in a runtime that has been disposed")
    }

    pub(crate) fn with<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&T) -> U) -> U
    where
        T: 'static,
    {
        with_runtime(runtime, |runtime| self.try_with(runtime, f).unwrap())
            .expect("tried to access a signal in a runtime that has been disposed")
    }

    fn update_value<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T) -> U) -> Option<U>
    where
        T: 'static,
    {
        with_runtime(runtime, |runtime| {
            let value = {
                let signals = runtime.signals.borrow();
                signals.get(*self).cloned()
            };
            if let Some(value) = value {
                let mut value = value.borrow_mut();
                if let Some(value) = value.downcast_mut::<T>() {
                    Some(f(value))
                } else {
                    debug_warn!(
                        "[Signal::update] failed when downcasting to Signal<{}>",
                        std::any::type_name::<T>()
                    );
                    None
                }
            } else {
                debug_warn!(
                    "[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.",
                    std::any::type_name::<T>()
                );
                None
            }
        })
        .unwrap_or_default()
    }

    pub(crate) fn update<T, U>(
        &self,
        runtime_id: RuntimeId,
        f: impl FnOnce(&mut T) -> U,
    ) -> Option<U>
    where
        T: 'static,
    {
        with_runtime(runtime_id, |runtime| {
            // update the value
            let updated = self.update_value(runtime_id, f);

            // notify subscribers
            if updated.is_some() {
                let subs = {
                    let subs = runtime.signal_subscribers.borrow();
                    let subs = subs.get(*self);
                    subs.map(|subs| subs.borrow().clone())
                };
                if let Some(subs) = subs {
                    for sub in subs {
                        let effect = {
                            let effects = runtime.effects.borrow();
                            effects.get(sub).cloned()
                        };
                        if let Some(effect) = effect {
                            effect.run(sub, runtime_id);
                        }
                    }
                }
            };
            updated
        })
        .unwrap_or_default()
    }

    pub(crate) fn update_with_no_effect<T, U>(
        &self,
        runtime: RuntimeId,
        f: impl FnOnce(&mut T) -> U,
    ) -> Option<U>
    where
        T: 'static,
    {
        // update the value
        self.update_value(runtime, f)
    }
}