March Docs

RingBuf

RingBuf: a fixed-capacity circular buffer with exactly one owner.

All operations are O(1) and allocation-free after make(), except snapshot and to_list, which are O(n).

Complexity: make O(n) — allocates backing array of [cap] slots push O(1) — writes newest; overwrites oldest when full pop O(1) — removes and returns oldest (FIFO) get / peek_* O(1) — indexed and endpoint access size / cap O(1) clear O(1) — resets cursors; does not zero the array snapshot O(n) — read the elements out, keep the buffer to_list / drop O(n) — end the buffer

Index convention: 0 = oldest element (natural FIFO drain order). When the buffer is full, push overwrites the oldest element silently.

Ownership contract: RingBuf(a) is always_linear. Every binding of a buffer must be consumed exactly once, and EVERY operation consumes the buffer: a mutator (push, clear) hands it back; a reader (pop, get, peek_*, size, cap, is_empty, is_full, snapshot) returns its answer beside it; a terminator (to_list, drop) ends it. Rebind on every call:

let rb = RingBuf.make(3)
let rb = RingBuf.push(rb, 1)
let (n, rb) = RingBuf.size(rb)
RingBuf.to_list(rb)

In actor state the buffer is a linear field, so a handler that reads it stores one back: { state with buf: RingBuf.push(state.buf, x) }. A send or a spawn argument is the consuming use, so a buffer can MOVE to another actor; it can never be aliased, captured by a closure, stored at module level or put in a Vault (each is a compile-time error), which is what lets the in-place write stay unconditional and allocation-free. Elements are ordinary values: a buffer silently overwrites its oldest element, so it cannot hold a value that must itself be consumed.

Design: specs/2026-10-06-linear-ringbuf-and-sendable-arrays-design.md.

Functions

fncapcap(rb : RingBuf(a)) : (Int, RingBuf(a))#

Fixed capacity of the buffer, beside the buffer. O(1). Set once at make() and never changes.

Examples

march> let (c, rb) = RingBuf.cap(RingBuf.make(8))
march> RingBuf.drop(rb)
march> let (c, rb) = RingBuf.cap(RingBuf.make(8))
march> c
8
fnclearclear(rb : RingBuf(a)) : RingBuf(a)#

Reset to empty in O(1) and hand the buffer back. Does not zero the backing array — elements previously written remain in memory until overwritten by future pushes. Cap is unchanged.

Examples

march> RingBuf.to_list(RingBuf.clear(RingBuf.push(RingBuf.make(4), 1)))
[]
fndropdrop(rb : RingBuf(a)) : Unit#

Finish the buffer, discarding its contents. O(n) for the elements it still holds. The one consuming use that returns nothing: every buffer must end in drop or to_list.

Examples

march> RingBuf.drop(RingBuf.make(4))
()
fngetget(rb : RingBuf(a), i : Int) : (Option(a), RingBuf(a))#

The element at logical index [i] (0 = oldest) beside the buffer, or (None, rb) when [i] is out of range. O(1).

Valid indices are 0 .. size-1. Negative indices or indices >= size give None.

Examples

march> let (x, rb) = RingBuf.get(RingBuf.push(RingBuf.make(4), 7), 0)
march> RingBuf.drop(rb)
march> let (x, rb) = RingBuf.get(RingBuf.push(RingBuf.make(4), 7), 0)
march> x
Some(7)
fnis_emptyis_empty(rb : RingBuf(a)) : (Bool, RingBuf(a))#

True when the buffer contains no elements, beside the buffer.

Examples

march> let (e, rb) = RingBuf.is_empty(RingBuf.make(4))
march> RingBuf.drop(rb)
march> let (e, rb) = RingBuf.is_empty(RingBuf.make(4))
march> e
true
fnis_fullis_full(rb : RingBuf(a)) : (Bool, RingBuf(a))#

True when size equals cap, beside the buffer. The next push will overwrite the oldest element.

Examples

march> let (f, rb) = RingBuf.is_full(RingBuf.make(4))
march> RingBuf.drop(rb)
march> let (f, rb) = RingBuf.is_full(RingBuf.make(4))
march> f
false
fnmakemake(cap : Int) : RingBuf(a)#

Create a new ring buffer with fixed [cap] capacity. Panics if [cap] <= 0.

Examples

march> RingBuf.drop(RingBuf.make(4))
()
march> let (c, rb) = RingBuf.cap(RingBuf.make(16))
march> RingBuf.drop(rb)
march> let (c, rb) = RingBuf.cap(RingBuf.make(16))
march> c
16
fnpeek_newestpeek_newest(rb : RingBuf(a)) : (Option(a), RingBuf(a))#

The newest element without removing it, beside the buffer; (None, rb) when empty. O(1). Equivalent to get(rb, size - 1).

Examples

march> let (x, rb) = RingBuf.peek_newest(RingBuf.push(RingBuf.make(4), 9))
march> RingBuf.drop(rb)
march> let (x, rb) = RingBuf.peek_newest(RingBuf.push(RingBuf.make(4), 9))
march> x
Some(9)
fnpeek_oldestpeek_oldest(rb : RingBuf(a)) : (Option(a), RingBuf(a))#

The oldest element without removing it, beside the buffer; (None, rb) when empty. O(1). Equivalent to get(rb, 0).

Examples

march> let (x, rb) = RingBuf.peek_oldest(RingBuf.make(4))
march> RingBuf.drop(rb)
march> let (x, rb) = RingBuf.peek_oldest(RingBuf.make(4))
march> x
None
fnpoppop(rb : RingBuf(a)) : (Option(a), RingBuf(a))#

Remove the oldest element (FIFO head). Returns (Some(x), rb), or (None, rb) when the buffer is empty. O(1).

Clears the vacated slot so the element's storage can be released.

Examples

march> let (x, rb) = RingBuf.pop(RingBuf.make(4))
march> RingBuf.drop(rb)
march> let (x, rb) = RingBuf.pop(RingBuf.make(4))
march> x
None
fnpushpush(rb : RingBuf(a), x : a) : RingBuf(a)#

Push [x] as the newest element and hand the buffer back. O(1), in place.

When the buffer is already full (size == cap), the oldest element is silently overwritten and size stays at cap.

Examples

march> RingBuf.to_list(RingBuf.push(RingBuf.make(4), 1))
[1]
fnsizesize(rb : RingBuf(a)) : (Int, RingBuf(a))#

Current number of elements, beside the buffer. O(1).

Starts at 0 and grows with each push up to cap. Once full, subsequent pushes overwrite the oldest element and size stays at cap.

Examples

march> let (n, rb) = RingBuf.size(RingBuf.make(8))
march> RingBuf.drop(rb)
march> let (n, rb) = RingBuf.size(RingBuf.make(8))
march> n
0
fnsnapshotsnapshot(rb : RingBuf(a)) : (List(a), RingBuf(a))#

The elements from oldest to newest as a List, beside the buffer. O(n).

The buffer is unchanged and stays live; the returned list is a copy, so pushes after this call do not affect it. Use to_list instead when the buffer is finished.

Examples

march> let (xs, rb) = RingBuf.snapshot(RingBuf.push(RingBuf.make(4), 1))
march> RingBuf.drop(rb)
march> let (xs, rb) = RingBuf.snapshot(RingBuf.push(RingBuf.make(4), 1))
march> xs
[1]
fnto_listto_list(rb : RingBuf(a)) : List(a)#

The elements from oldest to newest as a List, and the buffer ENDS. O(n).

This is the consuming use that finishes a buffer with its contents; use snapshot to read the elements out and keep pushing, or drop to finish a buffer whose contents are not needed.

Examples

march> RingBuf.to_list(RingBuf.push(RingBuf.push(RingBuf.make(4), 1), 2))
[1, 2]