reactive_cache/macros.rs
1/// Creates a reactive effect from a closure (and optionally a dependency collector).
2///
3/// The `effect!` macro is a convenient wrapper around
4/// [`reactive_cache::Effect::new`] and [`reactive_cache::Effect::new_with_deps`].
5/// It allows you to quickly register a reactive effect that automatically tracks
6/// dependencies and re-runs when they change.
7///
8/// # Forms
9///
10/// - `effect!(f)`
11/// Equivalent to calling [`Effect::new(f)`]. In this form, dependencies are
12/// automatically tracked while executing `f`.
13///
14/// - `effect!(f, deps)`
15/// Equivalent to calling [`Effect::new_with_deps(f, deps)`]. In this form,
16/// **dependency tracking is performed only when running `deps`**, not `f`.
17/// The closure `f` will still be executed when dependencies change, but its
18/// execution does **not** collect new dependencies.
19///
20/// # Requirements
21///
22/// - `f` must be a closure or function pointer that takes no arguments and returns `()`.
23/// - `deps` (if provided) must also be a closure or function pointer taking no arguments and returning `()`.
24///
25/// # Examples
26///
27/// ```rust
28/// use std::{cell::Cell, rc::Rc};
29/// use reactive_cache::effect;
30/// use reactive_macros::{ref_signal, signal};
31///
32/// signal!(static mut A: i32 = 1;);
33///
34/// // Track effect runs
35/// let counter = Rc::new(Cell::new(0));
36/// let counter_clone = counter.clone();
37///
38/// // `effect!(f)` form
39/// let e = effect!(move || {
40/// let _ = A(); // reading the signal
41/// counter_clone.set(counter_clone.get() + 1); // increment effect counter
42/// });
43///
44/// let ptr = Rc::into_raw(e); // actively leak to avoid implicitly dropping the effect
45///
46/// // Effect runs immediately upon creation
47/// assert_eq!(counter.get(), 1);
48///
49/// // Changing A triggers the effect again
50/// assert!(A_set(10));
51/// assert_eq!(counter.get(), 2);
52///
53/// // Setting the same value does NOT trigger the effect
54/// assert!(!A_set(10));
55/// assert_eq!(counter.get(), 2);
56///
57/// // `effect!(f, deps)` form
58/// let _ = effect!(
59/// || println!("effect body"),
60/// || println!("dependency collector")
61/// );
62/// ```
63///
64/// # SAFETY
65///
66/// The macro internally uses [`reactive_cache::Effect`], which relies on
67/// `static` tracking and is **not thread-safe**. Only use in single-threaded contexts.
68///
69/// # Warning
70///
71/// **Do not set any signal that is part of the same effect chain.**
72///
73/// Effects automatically run whenever one of their dependent signals changes.
74/// If an effect modifies a signal that it (directly or indirectly) observes,
75/// it creates a circular dependency. This can lead to:
76/// - an infinite loop of updates, or
77/// - conflicting updates that the system cannot resolve.
78///
79/// In the general case, it is impossible to automatically determine whether
80/// such an effect will ever terminate—this is essentially a version of the
81/// halting problem. Therefore, you must ensure manually that effects do not
82/// update signals within their own dependency chain.
83#[macro_export]
84macro_rules! effect {
85 ($f:expr) => {
86 reactive_cache::Effect::new($f)
87 };
88 ($f:expr, $f2:expr) => {
89 reactive_cache::Effect::new_with_deps($f, $f2)
90 };
91}