ReferenceControlArcConceptsStateful Variables

Stateful Variables

How to persist values across executions using Arc's stateful variables

Arc functions execute repeatedly in response to incoming data. Each execution starts fresh, with local variables reset to their initial values. Stateful variables let you preserve values across these executions.

Local vs Stateful Variables

Use := for local variables that reset each execution, and $= for stateful variables that persist:

func example() {
    local := 0      // resets to 0 every execution
    state $= 0      // persists across executions

    local = local + 1   // always becomes 1
    state = state + 1   // increments: 1, 2, 3, ...
}

The $= operator declares a stateful variable with an initial value. After the first execution, subsequent executions use the persisted value instead of the initial value.

Common Patterns

Counter

Track how many times a condition has occurred:

func count_events{threshold f64} (value f64) i64 {
    count $= 0
    if value > threshold {
        count = count + 1
    }
    return count
}

Accumulator

Sum values over time:

func accumulate(value f64) f64 {
    total $= 0.0
    total = total + value
    return total
}

Previous Value

Compare the current value to the previous one:

func rate_of_change(value f64) f64 {
    prev $= 0.0
    delta := value - prev
    prev = value
    return delta
}

Running Maximum

Track the highest value seen:

func running_max(value f64) f64 {
    max $= 0.0
    if value > max {
        max = value
    }
    return max
}

State Toggle

Maintain an on/off state:

func toggle(trigger u8) u8 {
    state $= 0
    if trigger {
        if state == 0 {
            state = 1
        } else {
            state = 0
        }
    }
    return state
}

Loops vs. Stateful Variables

Arc gives you two tools for repeated work, and they serve different purposes.

for loops do work within a single function call, iterating over a series, counting through a range, or repeating until a condition is met:

func apply_calibration(raw f64) f64 {
    offsets := [0.1, -0.05, 0.03]
    correction f64 := 0.0
    for x := offsets {
        correction = correction + x
    }
    return raw + correction
}

Stateful variables accumulate data across executions. The reactive model calls your function once per incoming value, and stateful variables carry the running totals between calls:

func running_average(value f64) f64 {
    total $= 0.0
    count $= 0
    total = total + value
    count = count + 1
    return total / f64(count)
}

Use for loops for computation within a single call. Use stateful variables for streaming computations that build up over time.

Reset on Stage Re-entry

When a stage in a sequence is re-entered, all stateful variables in that stage reset to their initial values. This ensures stages start fresh when you transition back to them.

sequence main {
    stage monitoring {
        // count resets to 0 each time we enter monitoring
        sensor -> count_events{threshold=100.0} -> event_count
        event_count > 10 => alert
    }

    stage alert {
        // handle alert...
        acknowledged => monitoring  // re-entering resets count
    }
}

If you need to preserve state across stage re-entries, put the stateful logic in a function that’s called from the top level (outside any sequence).

Type Inference

Stateful variables infer their type from the initial value, just like local variables. You can also specify the type explicitly:

func example() {
    // Type inferred from initial value
    count $= 0           // i64 (integer literal default)
    total $= 0.0         // f64 (float literal default)

    // Explicit type annotation
    precise f32 $= 0.0   // f32 instead of f64
}

Integer literals default to i64 and float literals default to f64. If you need a different type, add an explicit annotation.