Skip to main content

effect

Function effect 

Source
pub fn effect(f: impl Fn() + 'static) -> EffectHandle
Expand description

Creates an effect in the current thread’s default reactor.

The effect is scheduled immediately and then re-scheduled whenever one of its dependencies changes. Effects never run inline with the write that triggered them: they are flushed on the runtime’s microtask queue, so consecutive writes within one task coalesce into a single run. Use effect_with to route runs to a lane of your own instead.

A queued run first verifies its inputs: when only equality-suppressed memo updates occurred upstream, the run is skipped without executing the body. If an upstream computation panics during that verification, the panic propagates out of the flush and the effect re-marks itself dirty and re-queues, so it recovers once the underlying cause is fixed.

Each run is an ownership scope: crate::on_cleanup callbacks and nested effects registered during a run are disposed before the next run, and again when the effect itself is disposed.

§Panics

Panics — in every build, release included — when the effect runs more than 100 times within a single drain: the outermost flush and everything nested inside it, so a re-entrant Reactor::flush_now cannot reset the count. That indicates a divergent feedback loop: the effect writes state it (transitively) depends on with a value that never converges. The panic message names the effect’s creation site. Convergent feedback (for example clamping, where the rewritten value is suppressed by the signal’s equality check) is legal and settles well below the limit.

The guard is deliberately not scoped to debug_assertions. The alternative to panicking in a release build is not that the application carries on — it is a flush that never returns, with no panic, no log and nothing for a user to report.

Both behaviors above describe an effect with no enclosing error boundary. Under a crate::scope_catch, any panic from this effect — from its body or from dependency verification — is instead delivered to that boundary’s handler, and the effect is disposed rather than re-queued.

§Examples

use std::cell::RefCell;
use std::rc::Rc;

use adaptite::{effect, signal};
use runite::{queue_macrotask, run};

let seen = Rc::new(RefCell::new(Vec::new()));

queue_macrotask({
    let seen = Rc::clone(&seen);
    move || {
        let value = signal(1);
        effect({
            let seen = Rc::clone(&seen);
            let value = value.clone();
            move || seen.borrow_mut().push(value.get())
        })
        .leak();

        // Coalesces with the initial run: the effect observes only the final value.
        value.set(2);
    }
});
run();

assert_eq!(*seen.borrow(), [2]);