sycamore_reactive/
maybe_dyn.rs

1use std::borrow::Cow;
2use std::rc::Rc;
3
4use crate::*;
5
6/// Represents a value that can be either static or dynamic.
7///
8/// This is useful for cases where you want to accept a value that can be either static or dynamic,
9/// such as in component props.
10///
11/// A [`MaybeDyn`] value can be created from a static value or a closure that returns the value by
12/// using the [`From`] trait.
13///
14/// # Creating a `MaybeDyn`
15///
16/// You can create a `MaybeDyn` from a static value by using the [`MaybeDyn::Static`] variant.
17/// However, most of the times, you probably want to use the implementation of the `From<U>` trait
18/// for `MaybeDyn<T>`.
19///
20/// This trait is already implemented globally for signals and closures that return `T`. However,
21/// we cannot provide a blanket implementation for all types `T` to convert into `MaybeDyn<T>`
22/// because of specialization. Instead, we can only implement it for specific types.
23#[derive(Clone)]
24pub enum MaybeDyn<T>
25where
26    T: Into<Self> + 'static,
27{
28    /// A static value.
29    Static(T),
30    /// A dynamic value backed by a signal.
31    Signal(ReadSignal<T>),
32    /// A derived dynamic value.
33    Derived(Rc<dyn Fn() -> Self>),
34}
35
36impl<T: Into<Self> + 'static> MaybeDyn<T> {
37    /// Get the value by consuming itself. Unlike [`get_clone`](Self::get_clone), this method avoids
38    /// a clone if we are just storing a static value.
39    pub fn evaluate(self) -> T
40    where
41        T: Clone,
42    {
43        match self {
44            Self::Static(value) => value,
45            Self::Signal(signal) => signal.get_clone(),
46            Self::Derived(f) => f().evaluate(),
47        }
48    }
49
50    /// Get the value by copying it.
51    ///
52    /// If the type does not implement [`Copy`], consider using [`get_clone`](Self::get_clone)
53    /// instead.
54    pub fn get(&self) -> T
55    where
56        T: Copy,
57    {
58        match self {
59            Self::Static(value) => *value,
60            Self::Signal(value) => value.get(),
61            Self::Derived(f) => f().evaluate(),
62        }
63    }
64
65    /// Get the value by cloning it.
66    ///
67    /// If the type implements [`Copy`], consider using [`get`](Self::get) instead.
68    pub fn get_clone(&self) -> T
69    where
70        T: Clone,
71    {
72        match self {
73            Self::Static(value) => value.clone(),
74            Self::Signal(value) => value.get_clone(),
75            Self::Derived(f) => f().evaluate(),
76        }
77    }
78
79    /// Track the reactive dependencies, if it is dynamic.
80    pub fn track(&self) {
81        match self {
82            Self::Static(_) => {}
83            Self::Signal(signal) => signal.track(),
84            Self::Derived(f) => f().track(),
85        }
86    }
87
88    /// Tries to get the value statically or returns `None` if value is dynamic.
89    pub fn as_static(&self) -> Option<&T> {
90        match self {
91            Self::Static(value) => Some(value),
92            _ => None,
93        }
94    }
95}
96
97impl<T: Into<Self>, U: Into<MaybeDyn<T>> + Clone> From<ReadSignal<U>> for MaybeDyn<T> {
98    fn from(val: ReadSignal<U>) -> Self {
99        // Check if U == T, i.e. ReadSignal<U> is actually a ReadSignal<T>.
100        //
101        // If so, we use a trick to convert the generic type to the concrete type. This should be
102        // optimized out by the compiler to be zero-cost.
103        if let Some(val) =
104            (&mut Some(val) as &mut dyn std::any::Any).downcast_mut::<Option<ReadSignal<T>>>()
105        {
106            MaybeDyn::Signal(val.unwrap())
107        } else {
108            MaybeDyn::Derived(Rc::new(move || val.get_clone().into()))
109        }
110    }
111}
112
113impl<T: Into<Self>, U: Into<MaybeDyn<T>> + Clone> From<Signal<U>> for MaybeDyn<T> {
114    fn from(val: Signal<U>) -> Self {
115        Self::from(*val)
116    }
117}
118
119// TODO: add #[diagnostic::do_not_recommend] when it is stablised.
120impl<F, U, T: Into<Self>> From<F> for MaybeDyn<T>
121where
122    F: Fn() -> U + 'static,
123    U: Into<MaybeDyn<T>>,
124{
125    fn from(f: F) -> Self {
126        MaybeDyn::Derived(Rc::new(move || f().into()))
127    }
128}
129
130/// A macro that makes it easy to write implementations for `Into<MaybeDyn<T>>`.
131///
132/// Because of Rust orphan rules, you can only implement `Into<MaybeDyn<T>>` for types that are
133/// defined in the current crate. To work around this limitation, the newtype pattern can be used.
134///
135/// # Example
136///
137/// ```
138/// # use sycamore_reactive::*;
139///
140/// struct MyType;
141///
142/// struct OtherType;
143///
144/// impl From<OtherType> for MyType {
145///     fn from(_: OtherType) -> Self {
146///         todo!();
147///     }
148/// }
149///
150/// // You can also list additional types that can be converted to `MaybeDyn<MyType>`.
151/// impl_into_maybe_dyn!(MyType; OtherType);
152/// ```
153#[macro_export]
154macro_rules! impl_into_maybe_dyn {
155    ($ty:ty $(; $($from:ty),*)?) => {
156        impl From<$ty> for $crate::MaybeDyn<$ty> {
157            fn from(val: $ty) -> Self {
158                MaybeDyn::Static(val)
159            }
160        }
161
162        $crate::impl_into_maybe_dyn_with_convert!($ty; Into::into $(; $($from),*)?);
163    };
164}
165
166/// Create `From<U>` implementations for `MaybeDyn<T>` for a list of types.
167///
168/// Usually, you would use the [`impl_into_maybe_dyn!`] macro instead of this macro.
169#[macro_export]
170macro_rules! impl_into_maybe_dyn_with_convert {
171    ($ty:ty; $convert:expr $(; $($from:ty),*)?) => {
172        $(
173            $(
174                impl From<$from> for $crate::MaybeDyn<$ty> {
175                    fn from(val: $from) -> Self {
176                        MaybeDyn::Static($convert(val))
177                    }
178                }
179            )*
180        )?
181    };
182}
183
184impl_into_maybe_dyn!(Cow<'static, str>; &'static str, String);
185impl_into_maybe_dyn_with_convert!(
186    Option<Cow<'static, str>>; |x| Some(Into::into(x));
187    Cow<'static, str>, &'static str, String
188);
189impl_into_maybe_dyn_with_convert!(
190    Option<Cow<'static, str>>; |x| Option::map(x, Into::into);
191    Option<&'static str>, Option<String>
192);
193
194impl_into_maybe_dyn!(bool);
195
196impl_into_maybe_dyn!(f32);
197impl_into_maybe_dyn!(f64);
198
199impl_into_maybe_dyn!(i8);
200impl_into_maybe_dyn!(i16);
201impl_into_maybe_dyn!(i32);
202impl_into_maybe_dyn!(i64);
203impl_into_maybe_dyn!(i128);
204impl_into_maybe_dyn!(isize);
205impl_into_maybe_dyn!(u8);
206impl_into_maybe_dyn!(u16);
207impl_into_maybe_dyn!(u32);
208impl_into_maybe_dyn!(u64);
209impl_into_maybe_dyn!(u128);
210impl_into_maybe_dyn!(usize);
211
212impl<T> From<Option<T>> for MaybeDyn<Option<T>> {
213    fn from(val: Option<T>) -> Self {
214        MaybeDyn::Static(val)
215    }
216}
217
218impl<T> From<Vec<T>> for MaybeDyn<Vec<T>> {
219    fn from(val: Vec<T>) -> Self {
220        MaybeDyn::Static(val)
221    }
222}
223
224#[cfg(feature = "wasm-bindgen")]
225impl_into_maybe_dyn!(
226    wasm_bindgen::JsValue;
227    &'static str, String, bool, f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
228);
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn maybe_dyn_static() {
236        let value = MaybeDyn::<i32>::from(123);
237        assert!(value.as_static().is_some());
238        assert_eq!(value.get(), 123);
239        assert_eq!(value.get_clone(), 123);
240        assert_eq!(value.evaluate(), 123);
241    }
242
243    #[test]
244    fn maybe_dyn_signal() {
245        let _ = create_root(move || {
246            let signal = create_signal(123);
247            let value = MaybeDyn::<i32>::from(signal);
248            assert!(matches!(value, MaybeDyn::Signal(_)));
249
250            assert_eq!(value.get(), 123);
251            assert_eq!(value.get_clone(), 123);
252            assert_eq!(value.evaluate(), 123);
253        });
254    }
255
256    #[test]
257    fn maybe_dyn_signal_from() {
258        let _ = create_root(move || {
259            let signal = create_signal("abc");
260            let value = MaybeDyn::<Cow<'static, str>>::from(signal);
261            assert!(matches!(value, MaybeDyn::Derived(_)));
262
263            assert_eq!(value.get_clone(), "abc");
264            assert_eq!(value.evaluate(), "abc");
265        });
266    }
267
268    #[test]
269    fn maybe_dyn_derived() {
270        let value = MaybeDyn::<i32>::from(|| 123);
271        assert!(value.as_static().is_none());
272        assert_eq!(value.get(), 123);
273        assert_eq!(value.get_clone(), 123);
274        assert_eq!(value.evaluate(), 123);
275    }
276}