Skip to content
Refs and STM
step 1/4

Reading — step 1 of 4

Learn

~1 min readSTM and Lazy Sequences

For coordinated multi-cell mutation, Clojure has STM — Software Transactional Memory. Refs are STM-backed cells.

(def account-a (ref 1000))
(def account-b (ref 500))

(defn transfer! [from to amount]
    (dosync
        (alter from - amount)
        (alter to + amount)))

(transfer! account-a account-b 200)
@account-a    ;; 800
@account-b    ;; 700

dosync runs its body as a transaction:

  • Both alter calls happen, or neither
  • If another thread modified one of the refs in the middle, the transaction RETRIES
  • Reading a ref outside dosync is fine (always sees a consistent value)

alter vs commute vs ref-set:

  • alter f — apply f to current value, set to result. Reads guaranteed consistent.
  • commute f — same, but can commute with concurrent transactions if f is order-independent. Faster for counters.
  • ref-set v — directly replace value, regardless of current.

ACID-ish guarantees (in-memory):

  • Atomicity — all changes happen together
  • Consistency — invariants between refs hold
  • Isolation — transactions don't see partial state of others
  • (Durability — no, it's in-memory)

STM vs atoms vs agents:

  • atoms — single-cell, uncoordinated, fast (lock-free CAS)
  • refs + STM — multi-cell, coordinated, automatic retry on conflict
  • agents — async, single-cell, ordered updates

For 99% of Clojure code, atoms are enough. Use refs when you need to update multiple cells atomically and consistently — like the bank transfer example.

ensure — declare "I'm reading this ref and it must not change during my transaction":

(dosync
    (let [balance (ensure account-a)]
        ;; balance won't change under us during this transaction
        ...))

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…