State Management
Most programs need to keep track of something that changes over time: a counter, an accumulated list, a cache, a connection pool. In ordinary imperative code you would reach for a mutable variable — but as soon as that state is shared across concurrent fibers, plain mutation becomes a source of lost updates and race conditions.
Effect provides a small family of fiber-safe mutable references that let you keep state without leaving the world of effects. Every read and write is an effect, so state changes compose with the rest of your program and stay safe under concurrency.
import { Effect, Ref } from "effect"
const program = Effect.gen(function*() { // A reference to a single mutable value, created inside an effect. const counter = yield* Ref.make(0)
// Reads and writes are effects, so they sequence like any other step. yield* Ref.update(counter, (n) => n + 1) yield* Ref.update(counter, (n) => n + 1)
return yield* Ref.get(counter) // 2})Choosing a reference
Section titled “Choosing a reference”The three references differ in what kind of update they serialize and whether they let others observe changes. Pick the simplest one that fits.
-
Ref— a plain atomic cell. Each individual operation (get,set,update,modify) is atomic, which is enough for state whose next value is a pure function of the current value. This is what you reach for most of the time. -
SynchronizedRef— likeRef, but its update functions may themselves run effects, and those effectful updates are serialized so they never interleave. Use it when computing the next value requires an effect (a network call, a database lookup) that must not race with other updates. -
SubscriptionRef— a reference you can observe. In addition to holding the current value, it exposes aStreamof changes, so other parts of the program can react to every update. Use it for shared state that drives a UI, a dashboard, or any reactive consumer.
Notes on usage
Section titled “Notes on usage”Every reference operation is an effect, including construction with make, so a
reference is created inside an Effect.gen block or provided through a
service. A Ref is frequently wrapped in a service so
several subprograms can share the same state.
A single reference is always atomic for one operation, but a group of references is not updated transactionally. When you need several pieces of state to change together as one consistent unit, reach for transactions (STM) instead.