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_cache::prelude::*;
31/// use reactive_macros::signal;
32///
33/// signal!(static mut A: i32 = 1;);
34///
35/// // Track effect runs
36/// let counter = Rc::new(Cell::new(0));
37/// let counter_clone = counter.clone();
38///
39/// // `effect!(f)` form
40/// let e = effect!(move || {
41/// let _ = A().get(); // reading the signal
42/// counter_clone.set(counter_clone.get() + 1); // increment effect counter
43/// });
44///
45/// let ptr = Rc::into_raw(e); // actively leak to avoid implicitly dropping the effect
46///
47/// // Effect runs immediately upon creation
48/// assert_eq!(counter.get(), 1);
49///
50/// // Changing A triggers the effect again
51/// assert!(A().set(10));
52/// assert_eq!(counter.get(), 2);
53///
54/// // Setting the same value does NOT trigger the effect
55/// assert!(!A().set(10));
56/// assert_eq!(counter.get(), 2);
57///
58/// // `effect!(f, deps)` form
59/// let _ = effect!(
60/// || println!("effect body"),
61/// || println!("dependency collector")
62/// );
63/// ```
64///
65/// # SAFETY
66///
67/// The macro internally uses [`reactive_cache::Effect`], which relies on
68/// `static` tracking and is **not thread-safe**. Only use in single-threaded contexts.
69///
70/// # Warning
71///
72/// **Do not set any signal that is part of the same effect chain.**
73///
74/// Effects automatically run whenever one of their dependent signals changes.
75/// If an effect modifies a signal that it (directly or indirectly) observes,
76/// it creates a circular dependency. This can lead to:
77/// - an infinite loop of updates, or
78/// - conflicting updates that the system cannot resolve.
79///
80/// In the general case, it is impossible to automatically determine whether
81/// such an effect will ever terminate—this is essentially a version of the
82/// halting problem. Therefore, you must ensure manually that effects do not
83/// update signals within their own dependency chain.
84#[macro_export]
85macro_rules! effect {
86 ($f:expr) => {
87 $crate::Effect::new($f)
88 };
89 ($f:expr, $f2:expr) => {
90 $crate::Effect::new_with_deps($f, $f2)
91 };
92}