reactive_graph/
callback.rs

1//! Callbacks define a standard way to store functions and closures. They are useful
2//! for component properties, because they can be used to define optional callback functions,
3//! which generic props don’t support.
4//!
5//! The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
6//!
7//! # Types
8//! This modules implements 2 callback types:
9//! - [`Callback`](reactive_graph::callback::Callback)
10//! - [`UnsyncCallback`](reactive_graph::callback::UnsyncCallback)
11//!
12//! Use `SyncCallback` if the function is not `Sync` and `Send`.
13
14use crate::{
15    owner::{LocalStorage, StoredValue},
16    traits::{Dispose, WithValue},
17    IntoReactiveValue,
18};
19use std::{fmt, rc::Rc, sync::Arc};
20
21/// A wrapper trait for calling callbacks.
22pub trait Callable<In: 'static, Out: 'static = ()> {
23    /// calls the callback with the specified argument.
24    ///
25    /// Returns None if the callback has been disposed
26    fn try_run(&self, input: In) -> Option<Out>;
27    /// calls the callback with the specified argument.
28    ///
29    /// # Panics
30    /// Panics if you try to run a callback that has been disposed
31    fn run(&self, input: In) -> Out;
32}
33
34/// A callback type that is not required to be [`Send`] or [`Sync`].
35///
36/// # Example
37/// ```
38/// # use reactive_graph::prelude::*; use reactive_graph::callback::*;  let owner = reactive_graph::owner::Owner::new(); owner.set();
39/// let _: UnsyncCallback<()> = UnsyncCallback::new(|_| {});
40/// let _: UnsyncCallback<(i32, i32)> = (|_x: i32, _y: i32| {}).into();
41/// let cb: UnsyncCallback<i32, String> = UnsyncCallback::new(|x: i32| x.to_string());
42/// assert_eq!(cb.run(42), "42".to_string());
43/// ```
44pub struct UnsyncCallback<In: 'static, Out: 'static = ()>(
45    StoredValue<Rc<dyn Fn(In) -> Out>, LocalStorage>,
46);
47
48impl<In> fmt::Debug for UnsyncCallback<In> {
49    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
50        fmt.write_str("Callback")
51    }
52}
53
54impl<In, Out> Copy for UnsyncCallback<In, Out> {}
55
56impl<In, Out> Clone for UnsyncCallback<In, Out> {
57    fn clone(&self) -> Self {
58        *self
59    }
60}
61
62impl<In, Out> Dispose for UnsyncCallback<In, Out> {
63    fn dispose(self) {
64        self.0.dispose();
65    }
66}
67
68impl<In, Out> UnsyncCallback<In, Out> {
69    /// Creates a new callback from the given function.
70    pub fn new<F>(f: F) -> UnsyncCallback<In, Out>
71    where
72        F: Fn(In) -> Out + 'static,
73    {
74        Self(StoredValue::new_local(Rc::new(f)))
75    }
76
77    /// Returns `true` if both callbacks wrap the same underlying function pointer.
78    #[inline]
79    pub fn matches(&self, other: &Self) -> bool {
80        self.0.with_value(|self_value| {
81            other
82                .0
83                .with_value(|other_value| Rc::ptr_eq(self_value, other_value))
84        })
85    }
86}
87
88impl<In: 'static, Out: 'static> Callable<In, Out> for UnsyncCallback<In, Out> {
89    fn try_run(&self, input: In) -> Option<Out> {
90        self.0.try_with_value(|fun| fun(input))
91    }
92
93    fn run(&self, input: In) -> Out {
94        self.0.with_value(|fun| fun(input))
95    }
96}
97
98macro_rules! impl_unsync_callable_from_fn {
99    ($($arg:ident),*) => {
100        impl<F, $($arg,)* T, Out> From<F> for UnsyncCallback<($($arg,)*), Out>
101        where
102            F: Fn($($arg),*) -> T + 'static,
103            T: Into<Out> + 'static,
104            $($arg: 'static,)*
105        {
106            fn from(f: F) -> Self {
107                paste::paste!(
108                    Self::new(move |($([<$arg:lower>],)*)| f($([<$arg:lower>]),*).into())
109                )
110            }
111        }
112    };
113}
114
115impl_unsync_callable_from_fn!();
116impl_unsync_callable_from_fn!(P1);
117impl_unsync_callable_from_fn!(P1, P2);
118impl_unsync_callable_from_fn!(P1, P2, P3);
119impl_unsync_callable_from_fn!(P1, P2, P3, P4);
120impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5);
121impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6);
122impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7);
123impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8);
124impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
125impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
126impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
127impl_unsync_callable_from_fn!(
128    P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12
129);
130
131/// A callback type that is [`Send`] + [`Sync`].
132///
133/// # Example
134/// ```
135/// # use reactive_graph::prelude::*; use reactive_graph::callback::*;  let owner = reactive_graph::owner::Owner::new(); owner.set();
136/// let _: Callback<()> = Callback::new(|_| {});
137/// let _: Callback<(i32, i32)> = (|_x: i32, _y: i32| {}).into();
138/// let cb: Callback<i32, String> = Callback::new(|x: i32| x.to_string());
139/// assert_eq!(cb.run(42), "42".to_string());
140/// ```
141pub struct Callback<In, Out = ()>(
142    StoredValue<Arc<dyn Fn(In) -> Out + Send + Sync>>,
143)
144where
145    In: 'static,
146    Out: 'static;
147
148impl<In, Out> fmt::Debug for Callback<In, Out> {
149    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
150        fmt.write_str("SyncCallback")
151    }
152}
153
154impl<In, Out> Callable<In, Out> for Callback<In, Out> {
155    fn try_run(&self, input: In) -> Option<Out> {
156        self.0.try_with_value(|fun| fun(input))
157    }
158
159    fn run(&self, input: In) -> Out {
160        self.0.with_value(|f| f(input))
161    }
162}
163
164impl<In, Out> Clone for Callback<In, Out> {
165    fn clone(&self) -> Self {
166        *self
167    }
168}
169
170impl<In, Out> Dispose for Callback<In, Out> {
171    fn dispose(self) {
172        self.0.dispose();
173    }
174}
175
176impl<In, Out> Copy for Callback<In, Out> {}
177
178macro_rules! impl_callable_from_fn {
179    ($($arg:ident),*) => {
180        impl<F, $($arg,)* T, Out> From<F> for Callback<($($arg,)*), Out>
181        where
182            F: Fn($($arg),*) -> T + Send + Sync + 'static,
183            T: Into<Out> + 'static,
184            $($arg: Send + Sync + 'static,)*
185        {
186            fn from(f: F) -> Self {
187                paste::paste!(
188                    Self::new(move |($([<$arg:lower>],)*)| f($([<$arg:lower>]),*).into())
189                )
190            }
191        }
192    };
193}
194
195impl_callable_from_fn!();
196impl_callable_from_fn!(P1);
197impl_callable_from_fn!(P1, P2);
198impl_callable_from_fn!(P1, P2, P3);
199impl_callable_from_fn!(P1, P2, P3, P4);
200impl_callable_from_fn!(P1, P2, P3, P4, P5);
201impl_callable_from_fn!(P1, P2, P3, P4, P5, P6);
202impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7);
203impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8);
204impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
205impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
206impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
207impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12);
208
209impl<In: 'static, Out: 'static> Callback<In, Out> {
210    /// Creates a new callback from the given function.
211    #[track_caller]
212    pub fn new<F>(fun: F) -> Self
213    where
214        F: Fn(In) -> Out + Send + Sync + 'static,
215    {
216        Self(StoredValue::new(Arc::new(fun)))
217    }
218
219    /// Returns `true` if both callbacks wrap the same underlying function pointer.
220    #[inline]
221    pub fn matches(&self, other: &Self) -> bool {
222        self.0
223            .try_with_value(|self_value| {
224                other.0.try_with_value(|other_value| {
225                    Arc::ptr_eq(self_value, other_value)
226                })
227            })
228            .flatten()
229            .unwrap_or(false)
230    }
231}
232
233#[doc(hidden)]
234pub struct __IntoReactiveValueMarkerCallbackSingleParam;
235
236#[doc(hidden)]
237pub struct __IntoReactiveValueMarkerCallbackStrOutputToString;
238
239impl<I, O, F>
240    IntoReactiveValue<
241        Callback<I, O>,
242        __IntoReactiveValueMarkerCallbackSingleParam,
243    > for F
244where
245    F: Fn(I) -> O + Send + Sync + 'static,
246{
247    #[track_caller]
248    fn into_reactive_value(self) -> Callback<I, O> {
249        Callback::new(self)
250    }
251}
252
253impl<I, O, F>
254    IntoReactiveValue<
255        UnsyncCallback<I, O>,
256        __IntoReactiveValueMarkerCallbackSingleParam,
257    > for F
258where
259    F: Fn(I) -> O + 'static,
260{
261    #[track_caller]
262    fn into_reactive_value(self) -> UnsyncCallback<I, O> {
263        UnsyncCallback::new(self)
264    }
265}
266
267impl<I, F>
268    IntoReactiveValue<
269        Callback<I, String>,
270        __IntoReactiveValueMarkerCallbackStrOutputToString,
271    > for F
272where
273    F: Fn(I) -> &'static str + Send + Sync + 'static,
274{
275    #[track_caller]
276    fn into_reactive_value(self) -> Callback<I, String> {
277        Callback::new(move |i| self(i).to_string())
278    }
279}
280
281impl<I, F>
282    IntoReactiveValue<
283        UnsyncCallback<I, String>,
284        __IntoReactiveValueMarkerCallbackStrOutputToString,
285    > for F
286where
287    F: Fn(I) -> &'static str + 'static,
288{
289    #[track_caller]
290    fn into_reactive_value(self) -> UnsyncCallback<I, String> {
291        UnsyncCallback::new(move |i| self(i).to_string())
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::Callable;
298    use crate::{
299        callback::{Callback, UnsyncCallback},
300        owner::Owner,
301        traits::Dispose,
302        IntoReactiveValue,
303    };
304
305    struct NoClone {}
306
307    #[test]
308    fn clone_callback() {
309        let owner = Owner::new();
310        owner.set();
311
312        let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
313        let _cloned = callback;
314    }
315
316    #[test]
317    fn clone_unsync_callback() {
318        let owner = Owner::new();
319        owner.set();
320
321        let callback =
322            UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
323        let _cloned = callback;
324    }
325
326    #[test]
327    fn runback_from() {
328        let owner = Owner::new();
329        owner.set();
330
331        let _callback: Callback<(), String> = (|| "test").into();
332        let _callback: Callback<(i32, String), String> =
333            (|num, s| format!("{num} {s}")).into();
334        // Single params should work without needing the (foo,) tuple using IntoReactiveValue:
335        let _callback: Callback<usize, &'static str> =
336            (|_usize| "test").into_reactive_value();
337        let _callback: Callback<usize, String> =
338            (|_usize| "test").into_reactive_value();
339    }
340
341    #[test]
342    fn sync_callback_from() {
343        let owner = Owner::new();
344        owner.set();
345
346        let _callback: UnsyncCallback<(), String> = (|| "test").into();
347        let _callback: UnsyncCallback<(i32, String), String> =
348            (|num, s| format!("{num} {s}")).into();
349        // Single params should work without needing the (foo,) tuple using IntoReactiveValue:
350        let _callback: UnsyncCallback<usize, &'static str> =
351            (|_usize| "test").into_reactive_value();
352        let _callback: UnsyncCallback<usize, String> =
353            (|_usize| "test").into_reactive_value();
354    }
355
356    #[test]
357    fn sync_callback_try_run() {
358        let owner = Owner::new();
359        owner.set();
360
361        let callback = Callback::new(move |arg| arg);
362        assert_eq!(callback.try_run((0,)), Some((0,)));
363        callback.dispose();
364        assert_eq!(callback.try_run((0,)), None);
365    }
366
367    #[test]
368    fn unsync_callback_try_run() {
369        let owner = Owner::new();
370        owner.set();
371
372        let callback = UnsyncCallback::new(move |arg| arg);
373        assert_eq!(callback.try_run((0,)), Some((0,)));
374        callback.dispose();
375        assert_eq!(callback.try_run((0,)), None);
376    }
377
378    #[test]
379    fn callback_matches_same() {
380        let owner = Owner::new();
381        owner.set();
382
383        let callback1 = Callback::new(|x: i32| x * 2);
384        let callback2 = callback1;
385        assert!(callback1.matches(&callback2));
386    }
387
388    #[test]
389    fn callback_matches_different() {
390        let owner = Owner::new();
391        owner.set();
392
393        let callback1 = Callback::new(|x: i32| x * 2);
394        let callback2 = Callback::new(|x: i32| x + 1);
395        assert!(!callback1.matches(&callback2));
396    }
397
398    #[test]
399    fn unsync_callback_matches_same() {
400        let owner = Owner::new();
401        owner.set();
402
403        let callback1 = UnsyncCallback::new(|x: i32| x * 2);
404        let callback2 = callback1;
405        assert!(callback1.matches(&callback2));
406    }
407
408    #[test]
409    fn unsync_callback_matches_different() {
410        let owner = Owner::new();
411        owner.set();
412
413        let callback1 = UnsyncCallback::new(|x: i32| x * 2);
414        let callback2 = UnsyncCallback::new(|x: i32| x + 1);
415        assert!(!callback1.matches(&callback2));
416    }
417}