No matching definitions.

tur/trail

stdlib/trail.tur

backtrackable state: mutable cells whose writes undo on demand.

Since: SX1 (graduated from `--enable=backtrackable-state` 2026-08-29)

defeffect

Bt

(Bt :nil)

capability tag for mutating the backtracking trail.

nil (never performed; used only as a #fx{Bt} row annotation)

(defn bind-var [c : BtCell v : int] #fx{Bt} : bool (bt-set! c v))

Since: SX1 (checked since 2026-09-05)

defopaque

Mark

(Mark)

an opaque handle to a trail level.

defopaque

BtCell

(BtCell)

a mutable cell whose writes are trailed and undo on backtrack.

defopaque

GCell

(GCell)

a mutable cell that is NEVER trailed.

defn

bt-cell-new

(bt-cell-new [init : int] :)

allocate a trailed cell holding `init`.

initthe cell's starting value

A BtCell. Release with bt-cell-free.

(bt-get (bt-cell-new 0))  ; => 0

Since: SX1

defn

bt-lvar-new

(bt-lvar-new [unbound : int] :)

allocate a WRITE-ONCE trailed cell, initially unbound.

unboundthe value `bt-get` reads while the cell is unbound

A BtCell in the unbound state.

(bt-bound? (bt-lvar-new 0))  ; => false

Since: SX1

defn

bt-cell-free

(bt-cell-free [c : BtCell] :)

release a cell.

cthe cell
(bt-cell-free c)

Since: SX1

defn

bt-get

(bt-get [c : BtCell] :)

read a trailed cell.

cthe cell

Its current value.

(bt-get c)  ; => 0

Since: SX1

defn

bt-bound?

(bt-bound? [c : BtCell] :)

has this write-once cell been bound?

cthe cell

true once bound. Always true for a value cell.

(bt-bound? v)  ; => false

Since: SX1

defn

bt-set!

(bt-set! [c : BtCell v : int])

write a trailed cell.

cthe cell
vthe new value

true on success; false when `c` is a write-once cell that is already bound -- binding twice is a caller bug, not a silent overwrite.

(bt-set! c 42)  ; => true

Since: SX1

defn

g-cell-new

(g-cell-new [init : int] :)

allocate a cell that is never trailed.

initthe starting value

A GCell. Release with g-cell-free.

(g-get (g-cell-new 7))  ; => 7

Since: SX1

defn

g-cell-free

(g-cell-free [g : GCell] :)

release a never-trailed cell.

gthe cell
(g-cell-free g)

Since: SX1

defn

g-get

(g-get [g : GCell] :)

read a never-trailed cell.

gthe cell

Its current value.

(g-get g)  ; => 7

Since: SX1

defn

g-set!

(g-set! [g : GCell v : int])

write a never-trailed cell. The write survives every undo.

gthe cell
vthe new value
(g-set! g 7)

Since: SX1

defn

bt-mark

(bt-mark)

push a trail level and return a handle to it.

A Mark. Pass it to bt-undo-to! to restore, or bt-commit-to! to keep.

(let [m (bt-mark)] (bt-set! c 1) (bt-undo-to! m))

Since: SX1

defn

bt-undo-to!

(bt-undo-to! [m : Mark])

restore every trailed write made since `m`.

ma mark from bt-mark

true on success; false when `m` is STALE -- already undone, already committed, or from a level index that has since been reused. A stale undo is refused rather than performed: it is what a re-entered continuation produces, and quietly "succeeding" would restore words belonging to some other level's cells.

(bt-undo-to! m)  ; => true

Since: SX1

defn

bt-commit-to!

(bt-commit-to! [m : Mark])

drop the level but KEEP its writes.

ma mark from bt-mark

true on success; false when `m` is stale.

(bt-commit-to! m)  ; => true

Since: SX1

defn

bt-level

(bt-level :)

the current trail level, 0 when no mark is outstanding.

The level depth.

(bt-level)  ; => 0

Since: SX1

defn

bt-depth

(bt-depth :)

how many entries are on the trail right now.

The entry count.

(bt-depth)  ; => 1

Since: SX1

defn

untrailed-begin

(untrailed-begin)

suspend trailing until the matching untrailed-end.

(untrailed-begin) (bt-set! counter 1) (untrailed-end)

Since: SX1

defn

untrailed-end

(untrailed-end)

resume trailing.

(untrailed-end)

Since: SX1

defn

bt-scope

(bt-scope [A])

run `body` in a fresh trail level and undo its writes after.

bodya thunk. Taken ^fat, so a capturing lambda works -- which is the
only interesting case, since the body has to reach the cells.

Whatever `body` returned. The VALUE survives; the trailed WRITES do not. Returning something that points into state the scope just undid is the caller's problem -- this is a search primitive, not a memory-safe region.

(bt-scope (fn [] (do (bt-set! c 99) (bt-get c))))  ; => 99, c restored

TWO THINGS THAT COMPOSE SURPRISINGLY:

  1. `bt-commit-to!` inside the body escapes this scope ENTIRELY, not just
     one level. Commit means "promote to level 0" -- it discards the undo
     information rather than merging it into the enclosing level -- so a
     committed write survives every enclosing `bt-scope` too. That is
     deliberate (it is how a learned clause outlives a backjump), but it is
     the opposite of what "commit into the parent" suggests to a Prolog ear.

  2. A panic inside `body` skips the undo, leaving the trail at the inner
     level. A later `bt-undo-to!` on an OUTER mark still unwinds past it,
     but anything reading cells in between sees the body's writes. There is
     no unwind protection to hang the restore on.

The `bt-undo-to!` result is discarded: it reports only that the mark was
already spent, which inside this bracket means the body undid or committed
its own enclosing level -- covered by note 1.

Carries `#fx{Bt}` itself (it pushes and unwinds a level); `body` is
deliberately left without a declared row, so a body that also prints or
performs its own effects is not rejected at the bracket.

Since: SX2

defn

with-untrailed

(with-untrailed [A])

run `body` with trailing PAUSED, then resume.

bodya thunk, taken ^fat so it can capture the cells it writes.

Whatever `body` returned.

(let [m (bt-mark)]
    (with-untrailed (fn [] (bt-set! c 7)))
    (bt-undo-to! m)
    (bt-get c))                                      ; => 7, not restored

Same panic caveat as `bt-scope`, and worse: a panic inside `body` leaves
trailing paused for everything after it, so later writes silently stop
being undoable. Do not put fallible work here that a `bt-scope` could hold.

Since: SX2

defn

trail-reset!

(trail-reset!)

drop every level and entry WITHOUT running undo.

(trail-reset!)

Since: SX1

Internal definitions