Incular logoIncular
API ReferenceState & Async

Signal

Creation, tracked reads, equality-checked writes, and mutation rules.

Signal<T> (incular::prelude, from incular-runtime) is the application reactive primitive: single-threaded shared state. Reading inside a build subscribes that element; reading outside returns the value with no dependency. Clones share identity across windows on the same UI thread.

MethodEffect
new(value)Create with initial value (no Default)
get()Tracked read, clones value
get_untracked()Read without subscribing
set(value)Equality-checked write, returns bool
update(f)In-place mutation, always notifies
update_if_changed(f)Mutate, returns whether it changed
dependent_count()Current subscriber count

Creating

Signal::new(value) takes an explicit initial value (no Default) — see the runnable program below.

Create once — outside the build or via cx.state — then clone into builders and callbacks. Never create a fresh signal inside a build on every run.

Reading

get() tracks the dependency and clones; get_untracked() reads without subscribing (callbacks, logging); dependent_count() reports subscribers.

get() inside a builder subscribes the current element (or the current Memo/Effect). Outside a build it just returns the value.

Writing

set (equality-checked, returns bool), update (unconditional notify), update_if_changed — exercised via update in the runnable program above.

Rules:

  • Write only from event callbacks, Effects, or Actions — calling set while a widget builder runs panics.
  • Prefer set for replacements (skips redundant rebuilds), update for in-place mutation.

Devtools variants

Devtools variants

With the devtools feature: signal.devtools("counter") (named, T: Debug) and signal.devtools_editable() (editable for bool/i64/u64/f64/String). These require the feature flag and are intentionally not in the runnable program, which builds on default features.

Canonical pattern

use incular::prelude::*;

fn main() {
    let count = Signal::new(0u32);
    let app = Application::new(move |_cx| {
        let tapped = count.clone();
        let shown = count.clone();
        Container::builder()
            .padding(EdgeInsets::all(24.0))
            .alignment(Alignment::CENTER)
            .child(
                GestureDetector::new(Text::new(format!("Count: {}", shown.get())))
                    .on_tap(move || {
                        tapped.update(|c| *c += 1);
                    }),
            )
            .build()
            .into()
    })
    .expect("valid application");
    incular::run(app).expect("run application");
}

Related: Memo, Effect, Action, Tasks, Restoration.

On this page