No matching definitions.

tur/stm-sync

stdlib/stm-sync.tur

TMVar and TChan, built on tvar + check.

Both STM docs used to hand-sketch these from tvar/* and check. This is that sketch, made real and testable. See docs/archive/stm-tmvar-tchan-stdlib.md.

Load it explicitly: (load "stdlib/stm-sync.tur").

## Why these are macros, not functions

atomically requires a **syntactic** stm block -- (atomically (f x)) where f returns a transaction is a hard error ("atomically requires an stm block as argument"). So an STM action cannot be a value returned from a function; it has to be spliced into the transaction lexically, which is what a macro does.

That constraint shapes the whole API. Each operation comes in two forms:

`tmvar-take-stm`  -- the BODY only. Use inside your own `(stm ...)`,
                     composing several operations into ONE transaction
                     that retries as a unit.
`tmvar-take`      -- the same body wrapped in its own
                     `(atomically (stm ...))`. Use for a standalone
                     operation.

Reach for the -stm form whenever two operations must succeed together; two separate tmvar-take calls are two transactions, and another thread can interleave between them.

## The empty marker

A TVar holds a ptr, and an empty slot is the null pointer -- the same representation the guides sketched. In a pointer-valued world "no value" IS null, so this is not an :int sentinel standing in for a real type. The consequence is real though and worth stating plainly: **a TMVar cannot hold 0 / null as a legitimate value**, because it is indistinguishable from empty. Store a boxed value if you need the full range.

Since: 2026-08-20

defmacro

tmvar-new

(tmvar-new [v])

create a TMVar holding a value (full).

vthe initial value; must not be 0 (see "The empty marker" above)

A TVar handle serving as a full TMVar.

(let [mv (tmvar-new 42)] ...)

Since: 2026-08-20

defmacro

tmvar-new-empty

(tmvar-new-empty)

create an empty TMVar.

A TVar handle serving as an empty TMVar.

(let [mv (tmvar-new-empty)] ...)

Since: 2026-08-20

defmacro

tmvar-take-stm

(tmvar-take-stm [mv])

transaction BODY: block until full, then empty and return.

Splice into your own (stm ...) to compose with other operations in one transaction. The check makes the whole transaction retry while the slot is empty, so a waiter parks rather than spins.

(atomically (stm (tmvar-take-stm mv)))

Since: 2026-08-20

defmacro

tmvar-take

(tmvar-take [mv])

block until the TMVar is full, then empty it and return it.

Standalone form: runs in its own transaction. When two operations must succeed together, use tmvar-take-stm inside one (stm ...) instead -- two tmvar-take calls are two transactions and another thread can interleave.

mvthe TMVar

The value that was in the slot, as a ptr.

(let [mv (tmvar-new 42)]
  (println (:: (tmvar-take mv) :int)))  ; => 42

Since: 2026-08-20

defmacro

tmvar-put-stm

(tmvar-put-stm [mv v])

transaction BODY: block until empty, then fill.

(atomically (stm (tmvar-put-stm mv 7)))

Since: 2026-08-20

defmacro

tmvar-put

(tmvar-put [mv v])

block until the TMVar is empty, then fill it.

mvthe TMVar
vthe value to store; must not be 0
(let [mv (tmvar-new-empty)]
  (tmvar-put mv 7)
  (println (:: (tmvar-take mv) :int)))  ; => 7

Since: 2026-08-20

defmacro

tmvar-read-stm

(tmvar-read-stm [mv])

transaction BODY: block until full, return WITHOUT emptying.

The difference from tmvar-take-stm is the whole point: a reader that peeks leaves the value for the next taker.

(atomically (stm (tmvar-read-stm mv)))

Since: 2026-08-20

defmacro

tmvar-read

(tmvar-read [mv])

block until full, return the value without emptying.

mvthe TMVar

The value in the slot, as a ptr. The slot stays full.

(let [mv (tmvar-new 42)]
  (println (:: (tmvar-read mv) :int))   ; => 42
  (println (:: (tmvar-take mv) :int)))  ; => 42 -- still there

Since: 2026-08-20

defmacro

tmvar-full?

(tmvar-full? [mv])

is the TMVar currently full? Does not block.

mvthe TMVar

true when the slot holds a value.

(println (tmvar-full? (tmvar-new-empty)))  ; => false

Since: 2026-08-20

defn

tchan-cons-append

(tchan-cons-append [lst :int v :int] :int)

internal: append v to the end of cons list lst.

Returns a fresh list; the input is left alone, so a retried transaction that re-runs this cannot corrupt the queue it read.

defn

tchan-cons-head

(tchan-cons-head [lst :int] :int)

internal: first element, or -1 for an empty list.

defn

tchan-cons-tail

(tchan-cons-tail [lst :int] :int)

internal: the list without its first element.

defn

tchan-cons-len

(tchan-cons-len [lst :int] :int)

internal: number of elements.

defmacro

tchan-new

(tchan-new)

create an empty TChan.

A TVar handle serving as an empty channel.

(let [ch (tchan-new)] ...)

Since: 2026-08-20

defmacro

tchan-write-stm

(tchan-write-stm [ch v])

transaction BODY: append a value. Never blocks.

(atomically (stm (tchan-write-stm ch 1)))

Since: 2026-08-20

defmacro

tchan-write

(tchan-write [ch v])

append a value to the channel. Never blocks (unbounded).

chthe TChan
vthe value to append
(let [ch (tchan-new)] (tchan-write ch 1))

Since: 2026-08-20

defmacro

tchan-read-stm

(tchan-read-stm [ch])

transaction BODY: block until non-empty, then pop.

The check retries the whole transaction while the channel is empty, so a reader parks instead of spinning. Splice into your own (stm ...) to pop and do something else atomically.

(atomically (stm (tchan-read-stm ch)))

Since: 2026-08-20

defmacro

tchan-read

(tchan-read [ch])

block until the channel is non-empty, then pop the oldest.

FIFO: the value returned is the one written earliest.

chthe TChan

The oldest value in the channel.

(let [ch (tchan-new)]
  (tchan-write ch 1)
  (tchan-write ch 2)
  (println (tchan-read ch)))   ; => 1

Since: 2026-08-20

defmacro

tchan-len

(tchan-len [ch])

how many values are queued. Does not block.

chthe TChan

The number of queued values.

(println (tchan-len (tchan-new)))  ; => 0

Since: 2026-08-20

defmacro

tchan-empty?

(tchan-empty? [ch])

is the channel empty? Does not block.

chthe TChan

true when nothing is queued.

(println (tchan-empty? (tchan-new)))  ; => true

Since: 2026-08-20