This reference documents concrete built-in and standard-library APIs. The compiler-recognized contract reference is contracts.md.
| Type | Wasm repr | Description |
|---|---|---|
Int |
i64 | 64-bit integer |
Float |
f64 | 64-bit floating-point |
Bool |
i32 | Boolean (true / false) |
Byte |
i32 | Single byte (0–255) |
Void |
— | Unit type |
type Option<T> = { None, Some(T) }
Shorthand: T? is equivalent to Option<T>.
| Function | Signature | Description |
|---|---|---|
.is_some() |
fn<T>(opt: Option<T>) Bool |
True when opt is Some |
.is_none() |
fn<T>(opt: Option<T>) Bool |
True when opt is None |
.unwrap() |
fn<T>(opt: Option<T>) T |
Extract Some(v); traps if opt is None |
.unwrap_or(default) |
fn<T>(opt: Option<T>, default: T) T |
Extract Some(v) or return default |
.unwrap_or_else(f) |
fn<T>(opt: Option<T>, f: fn() T) T |
Extract Some(v) or lazily compute a default |
.map(f) |
fn<T, U>(opt: Option<T>, f: fn(T) U) Option<U> |
Transform Some(v) into Some(f(v)); leaves None unchanged |
.and_then(f) |
fn<T, U>(opt: Option<T>, f: fn(T) Option<U>) Option<U> |
Chain Option-producing steps without nesting |
.flatten() |
fn<T>(opt: Option<Option<T>>) Option<T> |
Remove one level of nested Option |
.filter(pred) |
fn<T>(opt: Option<T>, pred: fn(T) Bool) Option<T> |
Keep Some(v) only when pred(v) is true |
.or_some(other) |
fn<T>(opt: Option<T>, other: Option<T>) Option<T> |
Return opt if it is Some, otherwise return other |
.or_else(f) |
fn<T>(opt: Option<T>, f: fn() Option<T>) Option<T> |
Return opt if it is Some, otherwise lazily compute another option |
.inspect(f) |
fn<T>(opt: Option<T>, f: fn(T) Void) Option<T> |
Run f(v) for Some(v) and return the original option |
.ok_or(err) |
fn<T, E>(opt: T?, err: E) Result<T, E> |
Convert to Result: Some(v) → Ok(v), None → Err(err) |
.ok_or_else(f) |
fn<T, E>(opt: T?, f: fn() E) Result<T, E> |
Lazy variant — f() is only called when opt is None |
.transpose() |
fn<T, E>(opt: Option<Result<T, E>>) Result<Option<T>, E> |
Convert Option<Result<T,E>> into Result<Option<T>,E> |
.to_string() |
fn<T: Stringify>(opt: Option<T>) String |
Witnesses Stringify: Some(v) → Some(<v>), None → None; enables ${opt} interpolation |
try on Option: try opt extracts the Some value or propagates None via early return.
Only valid in functions returning Option<U>. Use .ok_or(err) to bridge to Result contexts.
fn first_valid(a: Int?, b: Int?) Int? {
x := try a // returns None if a is None
y := try b
.Some(x + y)
}
type Result<T, E> = { Ok(T), Err(E) }
Shorthand: T!E is equivalent to Result<T, E>.
Supports try sugar — v := try expr extracts the Ok value or propagates the Err.
| Function | Signature | Description |
|---|---|---|
.is_ok() |
fn<T, E>(res: Result<T, E>) Bool |
True when res is Ok |
.is_err() |
fn<T, E>(res: Result<T, E>) Bool |
True when res is Err |
.unwrap() |
fn<T, E: Stringify>(res: Result<T, E>) T |
Extract Ok(v); traps with the error value if res is Err(e) |
.unwrap_or(default) |
fn<T, E>(res: Result<T, E>, default: T) T |
Extract Ok(v) or return default |
.unwrap_or_else(f) |
fn<T, E>(res: Result<T, E>, f: fn(E) T) T |
Extract Ok(v) or map the error to a fallback value |
.map(f) |
fn<T, U, E>(res: Result<T, E>, f: fn(T) U) Result<U, E> |
Transform Ok(v) into Ok(f(v)); leaves Err(e) unchanged |
.map_err(f) |
fn<T, E, F>(res: Result<T, E>, f: fn(E) F) Result<T, F> |
Transform Err(e) into Err(f(e)); leaves Ok(v) unchanged |
.and_then(f) |
fn<T, U, E>(res: Result<T, E>, f: fn(T) Result<U, E>) Result<U, E> |
Chain Result-producing steps without nested Results |
.or_else(f) |
fn<T, E, F>(res: Result<T, E>, f: fn(E) Result<T, F>) Result<T, F> |
Recover from an error by lazily producing another Result |
.ok() |
fn<T, E>(res: Result<T, E>) Option<T> |
Convert Ok(v) to Some(v) and discard errors |
.err() |
fn<T, E>(res: Result<T, E>) Option<E> |
Convert Err(e) to Some(e) and discard success values |
.transpose() |
fn<T, E>(res: Result<Option<T>, E>) Option<Result<T, E>> |
Convert Result<Option<T>,E> into Option<Result<T,E>> |
.to_string() |
fn<T: Stringify, E: Stringify>(res: Result<T, E>) String |
Witnesses Stringify: Ok(v) → Ok(<v>), Err(e) → Err(<e>); enables ${res} interpolation |
Mutable reference cell for imperative state.
| Function | Signature | Description |
|---|---|---|
Cell.new |
fn<T>(v: T) Cell<T> |
Create a cell containing v |
.get() |
fn<T>(c: Cell<T>) T |
Read current value |
.set(v) |
fn<T>(c: Cell<T>, v: T) Void |
Overwrite value |
.update(f) |
fn<T>(c: Cell<T>, f: fn(T) T) Void |
Apply f to current value and store result |
Cooperative task handle. Tasks run on the same program thread and switch only at explicit task points (await, yield) or task-aware host operations such as time.sleep / stdin reads; they are not CPU-parallel.
| Function | Signature | Description |
|---|---|---|
Task.spawn |
fn<T>(f: fn() T) Task<T> |
Start f as a task and return a handle for its eventual result |
Task.await |
fn<T>(task: Task<T>) T |
Suspend until task completes, then return its result; propagates a task failure as a trap |
Task.yield |
fn() Void |
Yield control to the scheduler so another runnable task can make progress |
Record with fields { start: Int, end: Int, step: Int }. Iterable in for loops.
| Function | Signature | Description |
|---|---|---|
range |
fn(n: Int) Range |
Range [0, n) with step 1 |
range_from |
fn(start: Int, end: Int) Range |
Range [start, end) with step 1 |
range_step |
fn(start: Int, end: Int, step: Int) Range |
Range with custom step |
Lazy iterator type.
| Function | Signature | Description |
|---|---|---|
Iterator.next |
fn<T>(it: Iterator<T>) Option<IterItem<T>> |
Advance iterator |
Iterator.unfold |
fn<T,S>(seed: S, step: fn(S) UnfoldStep<T,S>) Iterator<T> |
Build iterator from seed + step function |
Iterator.map |
fn<T,U>(it: Iterator<T>, f: fn(T) U) Iterator<U> |
Lazy adapter that transforms each yielded value |
Iterator.filter |
fn<T>(it: Iterator<T>, pred: fn(T) Bool) Iterator<T> |
Lazy adapter that yields only matching values |
Iterator.take |
fn<T>(it: Iterator<T>, n: Int) Iterator<T> |
Lazy adapter that yields at most n values (n <= 0 yields empty) |
Iterator.drop |
fn<T>(it: Iterator<T>, n: Int) Iterator<T> |
Lazy adapter that drops the first n values (n <= 0 drops none) |
Iterator.take_while |
fn<T>(it: Iterator<T>, pred: fn(T) Bool) Iterator<T> |
Lazy adapter that yields values until pred returns false |
Iterator.drop_while |
fn<T>(it: Iterator<T>, pred: fn(T) Bool) Iterator<T> |
Lazy adapter that drops values while pred returns true, then yields the rest |
Iterator.to_vector |
fn<T>(it: Iterator<T>) Vector<T> |
Materialize iterator into a vector. Equivalent to collect x in it { x }. Traverses the full iterator — infinite iterators will not terminate. O(n) memory. The iterator itself is persistent and can be reused. |
Supporting types:
type IterItem<T> = .{ value: T, rest: Iterator<T> }
type UnfoldStep<T, S> = { Done, Yield(T, S) }
type Order = { Lt, Eq, Gt }
Comparison result type used by sort_by and primitive comparators.
| Function | Signature | Description |
|---|---|---|
Int.compare |
fn(a: Int, b: Int) Order |
Compare two integers |
Float.compare |
fn(a: Float, b: Float) Order |
Compare two floats |
String.compare |
fn(a: String, b: String) Order |
Lexicographic byte-order comparison |
Byte.compare |
fn(a: Byte, b: Byte) Order |
Compare two bytes by numeric value |
Order.to_string |
fn(o: Order) String |
Render as "Lt"/"Eq"/"Gt" |
Order satisfies the Stringify contract, so values interpolate ("${o}") and
work with generic helpers that require Stringify (e.g. assert.equal).
Comparators can be passed directly as function references:
nums.sort_by(Int.compare)
names.sort_by(String.compare)
| Function | Signature | Description |
|---|---|---|
Int.min |
fn(a: Int, b: Int) Int |
The smaller of two integers |
Int.max |
fn(a: Int, b: Int) Int |
The larger of two integers |
Int.clamp |
fn(n: Int, lo: Int, hi: Int) Int |
Clamp n into the inclusive range [lo, hi] (assumes lo <= hi) |
lo.max(0).min(width) // clamp via chained dot-calls
i.clamp(0, xs.len()) // or directly
| Function | Signature | Description |
|---|---|---|
print |
fn(s: String) Void |
Print to stdout (no newline) |
println |
fn(s: String) Void |
Print to stdout with newline |
eprint |
fn(s: String) Void |
Print to stderr (no newline) |
eprintln |
fn(s: String) Void |
Print to stderr with newline |
error |
fn(s: String) Void |
Trap with error message (unrecoverable) |
| Function | Signature | Description |
|---|---|---|
Int.to_string |
fn(n: Int) String |
Convert Int to String |
Float.to_string |
fn(f: Float) String |
Convert Float to String |
Bool.to_string |
fn(b: Bool) String |
Convert Bool to String |
String.to_string |
fn(s: String) String |
Identity (returns input) |
Int.from_string |
fn(s: String) Option<Int> |
Parse string to Int |
Float.from_string |
fn(s: String) Option<Float> |
Parse string to Float |
Int.to_float |
fn(n: Int) Float |
Convert Int to Float |
Float.to_int |
fn(f: Float) Int |
Convert integral Float to Int (traps if not integral) |
Float.bits |
fn(f: Float) Int |
Return the IEEE 754 bit pattern of the float as an Int |
Float.from_bits |
fn(n: Int) Float |
Reinterpret an IEEE 754 bit pattern as a Float |
Float.sqrt |
fn(f: Float) Float |
Square root, lowered to native f64.sqrt |
Float.abs |
fn(f: Float) Float |
Absolute value, lowered to native f64.abs |
Float.floor |
fn(f: Float) Float |
Round toward -∞, lowered to native f64.floor |
Float.ceil |
fn(f: Float) Float |
Round toward +∞, lowered to native f64.ceil |
Float.trunc |
fn(f: Float) Float |
Round toward zero, lowered to native f64.trunc |
Float.min |
fn(a: Float, b: Float) Float |
Lesser float, lowered to native f64.min |
Float.max |
fn(a: Float, b: Float) Float |
Greater float, lowered to native f64.max |
Float.round |
fn(f: Float) Float |
Round to nearest integer with halves toward +∞, matching JS Math.round |
String.from_char_code |
fn(n: Int) Option<String> |
Single-char string from integer code (ASCII range) |
String.from_byte |
fn(b: Byte) Option<String> |
Single-char string from byte value (ASCII range) |
String.from_code_point |
fn(n: Int) Option<String> |
String from Unicode code point (full range) |
Conversion functions can be used as first-class function references (e.g. nums.map(Int.to_string)). The dot-call form .to_string() also works on values directly.
Float.bits and Float.from_bits are low-level operations mainly useful for binary encoding, hashing, and other bit-exact work.
Primitive type representing a single byte (0–255). Returned by string indexing (s[i]) and byte iteration (for b in s).
| Function | Signature | Description |
|---|---|---|
Byte.to_int |
fn(b: Byte) Int |
Convert byte to integer |
Byte.from_int |
fn(n: Int) Option<Byte> |
Convert integer in range 0..255 to Byte |
Byte.to_string |
fn(b: Byte) String |
Convert byte to string representation |
Byte.compare |
fn(a: Byte, b: Byte) Order |
Compare two bytes by numeric value |
.in_range(lo, hi) |
fn(b: Byte, lo: Int, hi: Int) Bool |
Whether byte is in the inclusive ASCII range [lo, hi] |
.is_upper() |
fn(b: Byte) Bool |
Whether byte is an ASCII uppercase letter |
.is_lower() |
fn(b: Byte) Bool |
Whether byte is an ASCII lowercase letter |
.is_digit() |
fn(b: Byte) Bool |
Whether byte is an ASCII decimal digit |
.is_hex_digit() |
fn(b: Byte) Bool |
Whether byte is an ASCII hexadecimal digit |
.is_alpha() |
fn(b: Byte) Bool |
Whether byte is an ASCII letter |
.is_alnum() |
fn(b: Byte) Bool |
Whether byte is an ASCII letter or decimal digit |
.is_newline() |
fn(b: Byte) Bool |
Whether byte is LF (0x0A) |
.is_space() |
fn(b: Byte) Bool |
Whether byte is an ASCII whitespace character recognized by the lexer |
Strings are immutable, UTF-8 encoded, and GC-managed. String interpolation: "hello ${name}".
Byte-oriented model: Lengths, indices, and slicing all operate on byte offsets, not characters. Indexing (s[i]) returns a Byte. This is efficient but means multi-byte UTF-8 characters occupy multiple index positions. Use the Unicode helpers below for character-level operations.
| Method | Signature | Description |
|---|---|---|
.len() |
fn(s: String) Int |
Length in bytes |
.is_empty() |
fn(s: String) Bool |
Whether the string has no bytes |
s[i] |
— | Byte at byte offset i (returns Byte, traps on OOB) |
.get(i) |
fn(s: String, i: Int) Option<Byte> |
Safe byte lookup at byte offset |
.slice(start, end) |
fn(s: String, start: Int, end: Int) String |
Substring by byte offsets [start, end). Out-of-range indices are clamped to [0, len]. Traps if a clamped index falls mid-codepoint. Also written s[start..end] (the Sliceable contract) |
.concat(other) |
fn(s: String, other: String) String |
Concatenate two strings |
.char_code_at(i) |
fn(s: String, i: Int) Int |
Byte value at byte offset i (same as Byte.to_int(s[i])) |
.utf8_bytes() |
fn(s: String) Vector<Byte> |
Copy string bytes into a vector |
String.from_utf8 |
fn(bytes: Vector<Byte>) Option<String> |
Validate UTF-8 and create string |
String.from_byte |
fn(b: Byte) Option<String> |
Create a one-character string from an ASCII byte; returns None for non-ASCII bytes |
| Method | Signature | Description |
|---|---|---|
.index_of(needle) |
fn(s: String, needle: String) Option<Int> |
First byte offset of needle |
.contains(needle) |
fn(s: String, needle: String) Bool |
Whether s contains needle |
.starts_with(prefix) |
fn(s: String, prefix: String) Bool |
Prefix check |
.ends_with(suffix) |
fn(s: String, suffix: String) Bool |
Suffix check |
.split(sep) |
fn(s: String, sep: String) Vector<String> |
Split on separator (empty sep returns [s]) |
.lines() |
fn(s: String) Vector<String> |
Split on newlines (handles both \n and \r\n) |
.trim() |
fn(s: String) String |
Strip leading/trailing ASCII whitespace |
.strip_prefix(prefix) |
fn(s: String, prefix: String) Option<String> |
Remove prefix and return remainder, or None |
.strip_suffix(suffix) |
fn(s: String, suffix: String) Option<String> |
Remove suffix and return remainder, or None |
.count(needle) |
fn(s: String, needle: String) Int |
Count non-overlapping occurrences of needle |
.replace(old, new) |
fn(s: String, old: String, new_s: String) String |
Replace all non-overlapping occurrences |
| Method | Signature | Description |
|---|---|---|
.substring(start, end) |
fn(s: String, start: Int, end: Int) String |
Substring by character indices [start, end). Safe for multi-byte UTF-8. Indices clamped to [0, char_len]. O(n) |
.chars() |
fn(s: String) Iterator<String> |
Iterate Unicode scalars (each as a 1–4 byte String) |
.char_len() |
fn(s: String) Int |
Number of Unicode scalars |
.code_point_at(i) |
fn(s: String, i: Int) Option<Int> |
Code point at scalar index i (O(n)) |
.graphemes() |
fn(s: String) Iterator<String> |
Iterate extended grapheme clusters (user-perceived characters). Handles combining marks, ZWJ emoji sequences, and regional indicator flags via a simplified UAX #29 implementation. |
Iteration: for b in s { ... } iterates bytes (b: Byte). Use for ch in s.chars() { ... } to iterate Unicode scalars, or for g in s.graphemes() { ... } for user-perceived characters (grapheme clusters).
Qualified forms (String.len, String.trim, etc.) also work.
Persistent vector with structural sharing. Literal syntax: [1, 2, 3].
| Method | Signature | Description |
|---|---|---|
.len() |
fn<T>(v: Vector<T>) Int |
Number of elements |
.is_empty() |
fn<T>(v: Vector<T>) Bool |
Whether the vector has no elements |
.append(elem) |
fn<T>(v: Vector<T>, elem: T) Vector<T> |
Append element, return new vector. O(log n) amortized, effectively constant with the tail buffer |
.get(i) |
fn<T>(v: Vector<T>, i: Int) Option<T> |
Safe index lookup. O(log n) |
.set(i, val) |
fn<T>(v: Vector<T>, i: Int, val: T) Option<Vector<T>> |
Safe update at index. O(log n) |
.concat(other) |
fn<T>(v: Vector<T>, other: Vector<T>) Vector<T> |
Concatenate two vectors structurally. O(log n), sharing both operands' trees |
.slice(start, end) |
fn<T>(v: Vector<T>, start: Int, end: Int) Vector<T> |
Subvector [start, end). O(log n), sharing the source tree except boundary spines. Also written v[start..end] (the Sliceable contract) |
.gather(idx) |
fn<T>(xs: Vector<T>, idx: Vector<Int>) Vector<T> |
Bulk index: result[k] = xs[idx[k]], length idx.len(). Traps on OOB index. Permute/select/duplicate in one op |
.map(f) |
fn<A,B>(xs: Vector<A>, f: fn(A) B) Vector<B> |
Transform each element |
.filter(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Vector<A> |
Keep elements where f returns true |
.fold(init, f) |
fn<A,B>(xs: Vector<A>, init: B, f: fn(B,A) B) B |
Left fold |
.first() |
fn<A>(xs: Vector<A>) Option<A> |
First element, or .None if empty |
.last() |
fn<A>(xs: Vector<A>) Option<A> |
Last element, or .None if empty |
.drop_first() |
fn<A>(xs: Vector<A>) Vector<A> |
Vector without its first element, or empty if already empty. O(log n) via structural slice |
.drop_last() |
fn<A>(xs: Vector<A>) Vector<A> |
Vector without its last element, or empty if already empty. O(log n) worst-case, O(1) when shrinking the tail |
.take(n) |
fn<A>(xs: Vector<A>, n: Int) Vector<A> |
First n elements; clamps negative counts to zero and too-large counts to length |
.drop(n) |
fn<A>(xs: Vector<A>, n: Int) Vector<A> |
Skip first n elements; clamps negative counts to zero and too-large counts to length |
.take_while(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Vector<A> |
Longest prefix whose elements satisfy f |
.drop_while(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Vector<A> |
Suffix after the prefix whose elements satisfy f |
.find_map(f) |
fn<A,B>(xs: Vector<A>, f: fn(A) Option<B>) Option<B> |
First .Some produced by f, short-circuiting |
.count_where(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Int |
Count elements satisfying f |
.zip_with(other, f) |
fn<A,B,C>(a: Vector<A>, b: Vector<B>, f: fn(A,B) C) Vector<C> |
Combine elementwise, stopping at the shorter input |
.chunks(size) |
fn<T>(xs: Vector<T>, size: Int) Vector<View<Vector<T>>> |
Non-overlapping zero-copy windows over the vector; invalid sizes return empty |
.windows(size) |
fn<T>(xs: Vector<T>, size: Int) Vector<View<Vector<T>>> |
Sliding zero-copy windows over the vector; invalid or too-large sizes return empty |
.find(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Option<A> |
First element matching predicate |
.any(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Bool |
True if any element matches |
.all(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Bool |
True if all elements match |
.contains(elem) |
fn<A>(xs: Vector<A>, elem: A) Bool |
True if elem is in the vector |
.position(f) |
fn<A>(xs: Vector<A>, f: fn(A) Bool) Option<Int> |
Index of first element matching predicate |
.flat_map(f) |
fn<A,B>(xs: Vector<A>, f: fn(A) Vector<B>) Vector<B> |
Map each element to a vector and flatten |
.compact() |
fn<A>(xs: Vector<Option<A>>) Vector<A> |
Drop .None entries and unwrap .Some values |
.dedup() |
fn<A: Eq>(xs: Vector<A>) Vector<A> |
Remove adjacent duplicate elements |
.intersperse(sep) |
fn<A>(xs: Vector<A>, sep: A) Vector<A> |
Insert sep between elements |
.reverse() |
fn<A>(xs: Vector<A>) Vector<A> |
Reverse order |
.sort_by(cmp) |
fn<T>(xs: Vector<T>, cmp: fn(T,T) Order) Vector<T> |
Return a new sorted vector using comparator (e.g. xs.sort_by(Int.compare)) |
.join(sep) |
fn(xs: Vector<String>, sep: String) String |
Join strings with separator |
Vector.make |
fn<T>(size: Int, fill: T) Vector<T> |
Create vector of size filled with fill |
Indexing syntax: v[i] — unsafe, traps on out-of-bounds.
Index assignment: v[i] = x — sets element at index i.
Vectors are iterable: for x in v { ... } and for x, i in v { ... }.
Qualified forms (Vector.map, Vector.filter, etc.) also work.
Persistent double-ended queue. Import with use @std.queue for constructors and
qualified calls, and use @std.queue.{Queue} when naming the type directly.
The queue is immutable: push and pop operations return a new queue while sharing
structure where possible.
Queue<T> is designed for efficient operations at both ends. It uses vectors
internally and normally pushes/pops against vector tails; when one side empties,
it rebalances by reversing/splitting the remaining elements. Prefer conservative
amortized-performance assumptions until benchmark data is available. For tiny
collections, raw Vector<T> may still be faster due to lower overhead.
| Function / Method | Signature | Description |
|---|---|---|
queue.new() |
fn<T>() Queue<T> |
Create an empty queue |
queue.singleton(value) |
fn<T>(value: T) Queue<T> |
Create a queue with one value |
queue.from_vector(xs) |
fn<T>(xs: Vector<T>) Queue<T> |
Build a queue preserving vector order |
.to_vector() |
fn<T>(q: Queue<T>) Vector<T> |
Materialize values from front to back |
.len() |
fn<T>(q: Queue<T>) Int |
Number of values |
.is_empty() |
fn<T>(q: Queue<T>) Bool |
Whether the queue has no values |
.push_front(value) |
fn<T>(q: Queue<T>, value: T) Queue<T> |
Return a queue with value added at the front |
.push_back(value) |
fn<T>(q: Queue<T>, value: T) Queue<T> |
Return a queue with value added at the back |
.peek_front() |
fn<T>(q: Queue<T>) Option<T> |
Front value without removing it |
.peek_back() |
fn<T>(q: Queue<T>) Option<T> |
Back value without removing it |
.pop_front() |
fn<T>(q: Queue<T>) Option<Pop<T>> |
Remove the front value, returning { value, rest } |
.pop_back() |
fn<T>(q: Queue<T>) Option<Pop<T>> |
Remove the back value, returning { value, rest } |
Qualified forms (queue.push_back(q, x)) and method-call forms
(q.push_back(x)) both work.
Persistent priority queue, implemented as a pairing heap. Import with
use @std.heap for constructors and qualified calls, and
use @std.heap.{Heap} when naming the type directly. The heap is immutable:
every operation returns a new heap while sharing structure where possible.
Priority is defined by a comparator stored in the heap. The value for which the
comparator returns .Lt against all others is dequeued first, so a comparator
of Int.compare gives a min-heap and a reversed comparator
(fn(a, b) Order { b.compare(a) }) gives a max-heap. new_ord is a
shorthand for the Ord-contract min-heap. Because the comparator rides with the
heap, push / pop / merge take no comparator argument; only mix heaps built
with the same ordering.
Performance is amortized: push and merge are O(1), pop is O(log n).
The examples/performance/compiler/heap_* benchmarks confirm both a build-and-drain heapsort and a
mixed push/pop workload scale as n·log n with no quadratic blowup. For a
one-shot sort, Vector.sort_by is ~3–5× faster than build-then-drain; reach
for the heap when priorities arrive incrementally or you only need the top few.
| Function / Method | Signature | Description |
|---|---|---|
heap.new(cmp) |
fn<T>(cmp: fn(T, T) Order) Heap<T> |
Empty heap ordered by cmp |
heap.new_ord() |
fn<T: Ord>() Heap<T> |
Empty min-heap using the Ord contract |
heap.singleton(value, cmp) |
fn<T>(value: T, cmp: fn(T, T) Order) Heap<T> |
Heap of one value |
heap.from_vector(xs, cmp) |
fn<T>(xs: Vector<T>, cmp: fn(T, T) Order) Heap<T> |
Build a heap from a vector |
.to_vector() |
fn<T>(h: Heap<T>) Vector<T> |
Drain into a vector in priority order |
.len() |
fn<T>(h: Heap<T>) Int |
Number of values |
.is_empty() |
fn<T>(h: Heap<T>) Bool |
Whether the heap has no values |
.push(value) |
fn<T>(h: Heap<T>, value: T) Heap<T> |
Return a heap with value inserted |
.peek() |
fn<T>(h: Heap<T>) Option<T> |
Highest-priority value without removing it |
.pop() |
fn<T>(h: Heap<T>) Option<Pop<T>> |
Remove the highest-priority value, returning { value, rest } |
.merge(other) |
fn<T>(a: Heap<T>, b: Heap<T>) Heap<T> |
Combine two heaps (uses the left comparator) |
Qualified forms (heap.push(h, x)) and method-call forms (h.push(x)) both
work.
Persistent hash map. Keys must be Int, String, or Byte. keys(), dict iteration,
and helpers built on top of them preserve insertion order of first insertion; updating an
existing key keeps its position, and remove+reinsert appends it at the end.
| Method | Signature | Description |
|---|---|---|
Dict.new() |
fn<K,V>() Dict<K,V> |
Create empty dict |
.get(key) |
fn<K,V>(d: Dict<K,V>, key: K) Option<V> |
Look up value by key |
.set(key, value) |
fn<K,V>(d: Dict<K,V>, key: K, value: V) Dict<K,V> |
Insert/replace key-value pair, return new dict |
.len() |
fn<K,V>(d: Dict<K,V>) Int |
Number of entries |
.is_empty() |
fn<K,V>(d: Dict<K,V>) Bool |
Whether the dict has no entries |
.has(key) |
fn<K,V>(d: Dict<K,V>, key: K) Bool |
Check if key exists |
.keys() |
fn<K,V>(d: Dict<K,V>) Vector<K> |
All keys as a vector |
.values() |
fn<K,V>(d: Dict<K,V>) Vector<V> |
All values as a vector |
.remove(key) |
fn<K,V>(d: Dict<K,V>, key: K) Dict<K,V> |
Remove key, return new dict |
Lookup syntax: d[key] — returns Option<V>.
Assignment syntax: d[key] = value — sets key-value pair (sugar for Dict.set).
The free functions dict_get(d, key) and dict_get_unsafe(d, key) also exist.
Dicts are iterable: for k, v in d { ... }.
Qualified forms (Dict.values, etc.) also work.
Persistent set backed by a Dict<K, Void>. Elements must be Int, String, or
Byte (the same key constraint as Dict). Insertion order of first insertion is
preserved, so to_vector and iteration are deterministic. No import needed.
| Method | Signature | Description |
|---|---|---|
Set.new() |
fn<K>() Set<K> |
Create an empty set |
.insert(k) |
fn<K>(s: Set<K>, k: K) Set<K> |
Return a new set with k added (no-op if present) |
.remove(k) |
fn<K>(s: Set<K>, k: K) Set<K> |
Return a new set with k removed |
.contains(k) |
fn<K>(s: Set<K>, k: K) Bool |
Whether k is a member |
.len() |
fn<K>(s: Set<K>) Int |
Number of elements |
.is_empty() |
fn<K>(s: Set<K>) Bool |
Whether the set has no elements |
.to_vector() |
fn<K>(s: Set<K>) Vector<K> |
Elements as a vector, in insertion order |
.iter() |
fn<K>(s: Set<K>) Iterator<K> |
Iterate elements in insertion order |
Set.from_vector(xs) |
fn<K>(xs: Vector<K>) Set<K> |
Build a set from a vector, deduplicating |
.union(other) |
fn<K>(a: Set<K>, b: Set<K>) Set<K> |
Elements in either set (order: a then new from b) |
.intersection(other) |
fn<K>(a: Set<K>, b: Set<K>) Set<K> |
Elements in both sets (order from a) |
.difference(other) |
fn<K>(a: Set<K>, b: Set<K>) Set<K> |
Elements in a but not b (order from a) |
.is_subset(other) |
fn<K>(a: Set<K>, b: Set<K>) Bool |
Whether every element of a is in b |
Sets support ==/!= using membership equality, so insertion order does not
affect equality. Sets are directly iterable in insertion order; .to_vector()
returns the same order when a materialized vector is needed. Qualified forms
(Set.union, etc.) also work.
seen: Set<Int> = Set.new()
seen = seen.insert(3).insert(7)
seen.contains(7) // true
Set.from_vector([1, 1, 2]).len() // 2
for k in seen { ... } // iterate directly
+, -, *, /, %, unary -
Division by zero traps.
==, !=, <, <=, >, >=
and, or, ! (prefix not)
& (and), | (or), ^ (xor), << (shift left), >> (shift right)
Operate on the full 64-bit two's-complement representation. >> is an
arithmetic shift — it sign-extends, so -8 >> 1 is -4. Precedence follows
C: & binds tighter than ^, which binds tighter than |, and all three bind
looser than the shifts. Parenthesize when mixing with comparisons.
fn single_number(nums: Vector<Int>) Int {
acc := 0
for n in nums { acc = acc ^ n } // pairs cancel, unique value remains
acc
}
"text ${expr} more" — calls .to_string() on interpolated expressions.
Everything above (primitives, built-in types, I/O, type conversions, String/Vector/Dict methods, operators) is available as prelude — no import needed.
Only non-prelude stdlib modules require explicit imports: use @std.path, use @std.fs, use @std.io, use @std.io.stdin, use @std.io.stdout, use @std.io.stderr, use @std.proc, use @std.date, use @std.time, use @std.view, use @std.math, use @std.tuple, use @std.regexp, use @std.crypto, use @std.buffer.
Digest, MAC, and binary encoding helpers. The module is an umbrella over pure Twinkle implementations. MD5 and SHA-1 are legacy digest algorithms, included for compatibility with old protocols, checksums, and programming puzzles; do not use them for passwords, signatures, or collision-resistant security decisions. Prefer SHA-256 or HMAC-SHA-256 for new digest/MAC use cases.
The *_bytes digests (and the String entry points) route through a transient
linear-memory Buffer scratch internally — faster than the functional path even
counting the copy-in, so any program that hashes pulls in a Wasm memory section.
The *_buf variants skip the copy when the bytes already live in a reused Buffer.
type Digest = .{ bytes: Vector<Byte> }
| Function | Signature | Description |
|---|---|---|
crypto.md5 |
fn(input: String) Digest |
MD5 digest of a UTF-8 string |
crypto.md5_bytes |
fn(input: Vector<Byte>) Digest |
MD5 digest of raw bytes |
crypto.md5_buf |
fn(input: Buffer) Digest |
MD5 digest of the whole Buffer (buf.len() bytes); pair with buffer.from_bytes to amortize the copy when hashing reused data |
crypto.sha1 |
fn(input: String) Digest |
SHA-1 digest of a UTF-8 string |
crypto.sha1_bytes |
fn(input: Vector<Byte>) Digest |
SHA-1 digest of raw bytes |
crypto.sha1_buf |
fn(input: Buffer) Digest |
SHA-1 digest of the whole Buffer (buf.len() bytes) |
crypto.sha256 |
fn(input: String) Digest |
SHA-256 digest of a UTF-8 string |
crypto.sha256_bytes |
fn(input: Vector<Byte>) Digest |
SHA-256 digest of raw bytes |
crypto.sha256_buf |
fn(input: Buffer) Digest |
SHA-256 digest of the whole Buffer (buf.len() bytes) |
crypto.hmac_sha256 |
fn(key: String, message: String) Digest |
HMAC-SHA-256 over UTF-8 key/message strings |
crypto.hmac_sha256_bytes |
fn(key: Vector<Byte>, message: Vector<Byte>) Digest |
HMAC-SHA-256 over raw key/message bytes |
crypto.hex_encode |
fn(bytes: Vector<Byte>) String |
Lowercase hexadecimal encoding |
crypto.hex_decode |
fn(text: String) Result<Vector<Byte>, String> |
Decode uppercase/lowercase hexadecimal text |
crypto.base64_encode |
fn(bytes: Vector<Byte>) String |
Standard Base64 with = padding |
crypto.base64_decode |
fn(text: String) Result<Vector<Byte>, String> |
Decode standard padded Base64 |
.to_bytes() |
fn(d: Digest) Vector<Byte> |
Raw digest bytes |
.hex() |
fn(d: Digest) String |
Lowercase hexadecimal representation |
.base64() |
fn(d: Digest) String |
Standard Base64 representation |
.to_string() |
fn(d: Digest) String |
Stringify witness; renders .hex() |
use @std.crypto
digest := crypto.sha256("abc")
println(digest.hex()) // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Math helpers. Int.min, Int.max, and Int.clamp already live in the prelude;
this module adds number-theory, exponent, and floating-point helpers. Call
qualified, e.g. math.gcd(a, b).
The integer helpers are pure Twinkle. The constants are Twinkle literals. The
floating-point helpers bridge to the host's Math object via extern, so they
require the JS/Deno runner that backs target/twk run; those host imports are
tree-shaken away when only the integer helpers and constants are used. JS
Math constants are exported with lowercase Twinkle value names.
| Constant | Type | Description |
|---|---|---|
e |
Float |
Euler's number (Math.E) |
pi |
Float |
Pi (Math.PI) |
| Function | Signature | Description |
|---|---|---|
abs |
fn(n: Int) Int |
Absolute integer value (abs(min_i64) overflows) |
sign |
fn(n: Int) Int |
-1, 0, or 1 by integer sign |
gcd |
fn(a: Int, b: Int) Int |
Greatest common divisor (non-negative; gcd(0,0)=0) |
lcm |
fn(a: Int, b: Int) Int |
Least common multiple (0 when either is 0) |
pow |
fn(base: Int, exp: Int) Int |
Integer exponentiation; traps on negative exp; pow(0,0)=1 |
isqrt |
fn(n: Int) Int |
Floor of the integer square root; traps when n < 0 |
abs_float |
fn(x: Float) Float |
Floating-point absolute value (host Math.abs) |
sign_float |
fn(x: Float) Float |
Floating-point sign (host Math.sign) |
pow_float |
fn(base: Float, exp: Float) Float |
Floating-point exponentiation (host Math.pow) |
sqrt |
fn(x: Float) Float |
Square root (host Math.sqrt) |
cbrt |
fn(x: Float) Float |
Cube root (host Math.cbrt) |
hypot |
fn(x: Float, y: Float) Float |
Hypotenuse for two values (host Math.hypot) |
floor |
fn(x: Float) Float |
Largest integer ≤ x (host Math.floor) |
ceil |
fn(x: Float) Float |
Smallest integer ≥ x (host Math.ceil) |
round |
fn(x: Float) Float |
Nearest integer, ties toward +∞ (host Math.round) |
trunc |
fn(x: Float) Float |
Integer part, removing fractional digits (host Math.trunc) |
fround |
fn(x: Float) Float |
Round to nearest 32-bit float value (host Math.fround) |
sin / cos / tan |
fn(x: Float) Float |
Trigonometric functions, radians (host Math.*) |
asin / acos / atan |
fn(x: Float) Float |
Inverse trigonometric functions, radians (host Math.*) |
atan2 |
fn(y: Float, x: Float) Float |
Quadrant-aware arc-tangent (host Math.atan2) |
sinh / cosh / tanh |
fn(x: Float) Float |
Hyperbolic functions (host Math.*) |
asinh / acosh / atanh |
fn(x: Float) Float |
Inverse hyperbolic functions (host Math.*) |
exp / expm1 |
fn(x: Float) Float |
e^x and e^x - 1 (host Math.*) |
log / log1p |
fn(x: Float) Float |
Natural log and ln(1+x) (host Math.*) |
log2 / log10 |
fn(x: Float) Float |
Base-2 and base-10 logarithms (host Math.*) |
random |
fn() Float |
Pseudorandom number in [0, 1) (host Math.random) |
use @std.math
math.gcd(12, 18) // 6
math.pow(2, 10) // 1024
math.isqrt(99) // 9
math.pi // 3.141592653589793
math.sin(math.pi / 2.0) // 1.0
Pure-Twinkle regular expressions for structured text parsing. Compile once with
regexp.compile/regexp.must (or compile_with/must_with for options), then
use the inherent methods on Regexp. Matches use Unicode scalar offsets;
captures are 1-based (group(0) is the whole match). Matching is linear-time
(Pike VM): there is no backtracking, so patterns never blow up on adversarial
input.
| Type / Function | Signature | Description |
|---|---|---|
RegexError |
.{ pos: Int, message: String } |
Compile error; pos is a scalar offset in the pattern |
CompileOptions |
.{ ignore_case: Bool, multiline: Bool, dotall: Bool } |
Global flags; also expressible inline ((?i)/(?m)/(?s)) |
Match |
.{ start: Int, end: Int, groups: Vector<String?>, group_names: ... } |
Successful match with pre-materialized capture text |
regexp.compile(pattern) |
fn(pattern: String) Result<Regexp, RegexError> |
Compile a pattern (all flags off) |
regexp.compile_with(pattern, opts) |
fn(pattern: String, opts: CompileOptions) Result<Regexp, RegexError> |
Compile with global flags |
regexp.must(pattern) |
fn(pattern: String) Regexp |
Compile or trap with regexp:${pos}: ... |
regexp.must_with(pattern, opts) |
fn(pattern: String, opts: CompileOptions) Regexp |
Compile with flags or trap |
.test(s) |
fn(re: Regexp, s: String) Bool |
True if the pattern matches anywhere |
.find(s) |
fn(re: Regexp, s: String) Match? |
Leftmost match, if any |
.find_all(s) |
fn(re: Regexp, s: String) Iterator<Match> |
Non-overlapping matches, including empty-match progress |
.replace(s, repl) |
fn(re: Regexp, s: String, repl: String) String |
Replace the first match |
.replace_all(s, repl) |
fn(re: Regexp, s: String, repl: String) String |
Replace every match |
Match.group(i) |
fn(m: Match, i: Int) String? |
Capture text by index, or .None if absent/unmatched |
Match.group_named(name) |
fn(m: Match, name: String) String? |
Capture text by group name, or .None |
Match.text() |
fn(m: Match) String |
Whole-match text |
Supported syntax: literals, ., character classes and ranges ([abc], [a-z],
[^...]), predefined classes (\d \w \s and uppercase negations), greedy and
lazy quantifiers (* + ? {m} {m,} {m,n}, each with a trailing ? for the lazy
form), capturing, non-capturing, and named groups, alternation, ^/$, and
escapes (\n \t \r \f \v \\ and escaped metacharacters). Replacement templates
expand $0..$9, with $$ for a literal dollar.
Flags are ASCII-only and can be set globally (via CompileOptions) or inline:
ignore_case/(?i)— case-insensitive matching.multiline/(?m)—^also matches after\n,$also before\n.dotall/(?s)—.also matches\n.
Inline flags apply to the rest of the pattern when leading ((?im)abc), or to a
scoped group: (?i:abc) enables, (?-i:abc) disables, (?i-m:abc) combines.
Scoped flags override global options within their extent.
Named groups use (?<name>…) or (?P<name>…); names must be snake_case
(lowercase first) and unique. A named group is also a numbered group in source
order, so group(1) and group_named("id") can refer to the same capture.
Not supported (by design, to keep matching linear-time): backreferences
(\1, \k<name>), lookaround ((?=…), (?!…), (?<=…), (?<!…)), and other
backtracking-engine features (atomic groups, possessive quantifiers, conditional
patterns).
Raw string literals (r"…") avoid doubling backslashes in patterns; both
spellings compile identically (r"\d+" is the same pattern as "\\d+").
use @std.regexp
re := regexp.must(r"(?<n>\d+) (?<color>red|green|blue)")
for m in re.find_all("Game 1: 3 blue, 4 red") {
n := Int.from_string(m.group_named("n").unwrap()).unwrap()
color := m.group_named("color").unwrap()
println("${n} ${color}")
}
// Case-insensitive via options, or inline (?i):
regexp.must_with("error", .{ ignore_case: true, multiline: false, dotall: false }).test("ERROR") // true
regexp.must("(?i)error").test("ERROR") // true
// Lazy quantifier stops at the first match:
regexp.must("<.*?>").find("<a><b>").unwrap().text() // "<a>"
regexp.must(r"mul\((\d+),(\d+)\)").replace_all("mul(2,4)", "$1*$2") // "2*4"
Path manipulation (string-based, no I/O).
| Function | Signature | Description |
|---|---|---|
is_absolute |
fn(path: String) Bool |
Starts with / |
join |
fn(base: String, part: String) String |
Join two path segments |
join_all |
fn(parts: Vector<String>) String |
Join multiple segments |
dirname |
fn(path: String) String |
Directory component |
basename |
fn(path: String) String |
Filename component |
stem |
fn(path: String) String |
Filename without extension |
extension |
fn(path: String) String |
Extension including dot |
normalize |
fn(path: String) String |
Canonicalize (resolve . and ..) |
File system operations. Functions return Result types with FsError.
type FsError = { NotFound, PermissionDenied, InvalidUtf8, Other(String) }
type EntryKind = { File, Dir, Other }
type DirEntry = .{ name: String, kind: EntryKind }
| Function | Signature | Description |
|---|---|---|
read_bytes |
fn(path: String) Vector<Byte>!FsError |
Read raw file bytes |
read_buffer |
fn(path: String) Buffer!FsError |
Read raw file bytes directly into a linear-memory buffer |
read_text |
fn(path: String) String!FsError |
Read UTF-8 text (read_bytes + decode) |
write_text |
fn(path: String, content: String) !FsError |
Write string to file |
write_bytes |
fn(path: String, bytes: Vector<Byte>) !FsError |
Write bytes to file |
write_buffer |
fn(path: String, buf: Buffer) !FsError |
Write bytes directly from a linear-memory buffer |
mkdirp |
fn(path: String) !FsError |
Create directory (and parents) |
list_dir |
fn(path: String) Vector<DirEntry>!FsError |
List directory entries |
exists |
fn(path: String) Bool |
Check if path exists |
Shared and stream-specific terminal I/O helpers. Stdin reads may suspend cooperatively under a task-capable async runtime, but they remain I/O APIs rather than Task APIs.
@std.io:
type Fd = { Stdin, Stdout, Stderr }
| Function | Signature | Description |
|---|---|---|
is_terminal |
fn(fd: Fd) Bool |
True when the selected standard stream is attached to a terminal |
write_bytes |
fn(fd: Fd, bytes: Vector<Byte>) Void |
Write raw bytes to stdout or stderr (Stdin traps) |
write_text |
fn(fd: Fd, text: String) Void |
Write UTF-8 text to stdout or stderr (Stdin traps) |
@std.io.stdin:
| Function | Signature | Description |
|---|---|---|
read_chunk |
fn(max_bytes: Int) Vector<Byte> |
Read up to max_bytes from stdin; returns an empty vector at EOF |
read_timeout |
fn(max_bytes: Int, timeout_ms: Int) Vector<Byte> |
Read up to max_bytes, waiting at most timeout_ms; returns empty on timeout or EOF |
eof |
fn() Bool |
True after stdin reaches EOF |
is_terminal |
fn() Bool |
True when stdin is attached to a terminal |
@std.io.stdout:
| Function | Signature | Description |
|---|---|---|
write_bytes |
fn(bytes: Vector<Byte>) Void |
Write raw bytes to stdout |
write_text |
fn(text: String) Void |
Write UTF-8 text to stdout |
is_terminal |
fn() Bool |
True when stdout is attached to a terminal |
@std.io.stderr:
| Function | Signature | Description |
|---|---|---|
write_bytes |
fn(bytes: Vector<Byte>) Void |
Write raw bytes to stderr |
write_text |
fn(text: String) Void |
Write UTF-8 text to stderr |
is_terminal |
fn() Bool |
True when stderr is attached to a terminal |
Process and environment.
| Function | Signature | Description |
|---|---|---|
args |
fn() Vector<String> |
Command-line arguments |
env |
fn(name: String) Option<String> |
Environment variable lookup |
cwd |
fn() String |
Current working directory |
exit |
fn(code: Int) |
Exit process (never returns) |
Compatibility timing utilities. Prefer @std.time.now() for elapsed/runtime timing; date is reserved for calendar/date APIs over time.
| Function | Signature | Description |
|---|---|---|
now |
fn() Float |
Current time as milliseconds since the time origin (performance.now() in Node/browser; ms since Unix epoch in the interpreter) |
Runtime timing utilities.
| Function | Signature | Description |
|---|---|---|
now |
fn() Float |
Monotonic-ish milliseconds since the runtime time origin |
sleep |
fn(ms: Int) Void |
Suspend for at least ms milliseconds under the async/JSPI runtime |
A read-only, zero-copy window View<C> over any IndexRead backing (a Vector,
a String, or another View). Element reads delegate through the contract, so
at is a direct backing read plus an integer add; the window ops are O(1) and
share the one backing. A View itself satisfies IndexRead<E>. Requires
use @std.view (and use @std.view.{View} to name the type). The element type
E follows from the backing via the functional dependency. See
plans/view.md.
API shape: a View method returns a View (cheaper sub-window), a scalar /
Option (a reduction), or an Iterator — it never silently materializes a
Vector of transformed elements. To transform or collect, opt in explicitly:
v.iter().map(f).filter(g).to_vector() (lazy, via Iterator) or v.to_vector()
(eager). Iterate with for x in v.iter(), mirroring the buffer views
(@std.buffer). The only Vector-returning members are the explicit
materializer to_vector and the structural windowers chunks / windows
(which yield sub-Views, not copied elements).
| Function | Signature | Description |
|---|---|---|
view.from(c) |
fn<C: IndexRead<E>, E>(c: C) View<C> |
Wrap a whole backing in a window (O(1)) |
.len() |
fn<C>(v: View<C>) Int |
Number of elements in the window |
.is_empty() |
fn<C>(v: View<C>) Bool |
True when the window has no elements |
.at(i) |
fn<C: IndexRead<E>, E>(v: View<C>, i: Int) E |
Element at window-relative index (traps OOB); backs IndexRead |
.first() |
fn<C: IndexRead<E>, E>(v: View<C>) Option<E> |
First element, or .None if empty |
.last() |
fn<C: IndexRead<E>, E>(v: View<C>) Option<E> |
Last element, or .None if empty |
.drop_first() |
fn<C>(v: View<C>) View<C> |
Drop the first element (O(1), shares backing; total) |
.drop_last() |
fn<C>(v: View<C>) View<C> |
Drop the last element (O(1), shares backing; total) |
.sub(a, b) |
fn<C>(v: View<C>, a: Int, b: Int) View<C> |
Relative sub-window [a, b) (O(1), shares backing; total — endpoints clamp into [0, len()], so out-of-range or reversed args yield a valid/empty window) |
.slice(a, b) |
fn<C>(v: View<C>, a: Int, b: Int) View<C> |
Alias for .sub; the Sliceable satisfier backing v[a..b] |
.to_vector() |
fn<C: IndexRead<E>, E>(v: View<C>) Vector<E> |
Materialize the window into an owned Vector |
.iter() |
fn<C: IndexRead<E>, E>(v: View<C>) Iterator<E> |
Zero-copy iterator over the window; backs for x in v.iter() and lazy iter()-driven transformation chains |
.fold(init, f) |
fn<C: IndexRead<E>, E, B>(v: View<C>, init: B, f: fn(B, E) B) B |
Left-fold over the window |
.take(n) |
fn<C>(v: View<C>, n: Int) View<C> |
First n elements (O(1), shares backing; clamps like sub) |
.drop(n) |
fn<C>(v: View<C>, n: Int) View<C> |
Skip first n elements (O(1), shares backing; clamps like sub) |
.take_while(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) View<C> |
Prefix whose elements satisfy f (O(1) result, shares backing) |
.drop_while(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) View<C> |
Suffix after the prefix whose elements satisfy f (O(1) result, shares backing) |
.find_map(f) |
fn<C: IndexRead<E>, E, U>(v: View<C>, f: fn(E) Option<U>) Option<U> |
First .Some produced by f, short-circuiting |
.count_where(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) Int |
Count elements satisfying f |
.chunks(size) |
fn<C>(v: View<C>, size: Int) Vector<View<C>> |
Non-overlapping contiguous windows; invalid sizes return empty |
.windows(size) |
fn<C>(v: View<C>, size: Int) Vector<View<C>> |
Sliding contiguous windows; invalid or too-large sizes return empty |
.find(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) Option<E> |
First element matching predicate |
.any(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) Bool |
True if any element matches |
.all(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) Bool |
True if all elements match |
.position(f) |
fn<C: IndexRead<E>, E>(v: View<C>, f: fn(E) Bool) Option<Int> |
Index of first matching element |
Ad-hoc grouping of a few values without declaring a domain record. Pair<A, B>
is the common case; Triple<A, B, C> is the escalation. Both are nominal
records, so they get conditional structural ==/!= for free when their fields
satisfy Eq, and both satisfy Stringify ((a, b) / (a, b, c)). Use a named
record instead when the fields carry meaningful API names.
Like @std.view, the full surface is two import lines: use @std.tuple for the
tuple.pair / tuple.triple constructors, and use @std.tuple.{Pair, Triple}
to name the types in annotations. Triple is transparently re-exported from a
submodule (so each arity can own its to_string); you never need to import
@std.tuple.triple directly.
| Function | Signature | Description |
|---|---|---|
tuple.pair(a, b) |
fn<A, B>(first: A, second: B) Pair<A, B> |
Construct a Pair |
tuple.triple(a, b, c) |
fn<A, B, C>(first: A, second: B, third: C) Triple<A, B, C> |
Construct a Triple |
.swap() |
fn<A, B>(p: Pair<A, B>) Pair<B, A> |
Swap the two components |
.to_string() |
fn<A: Stringify, B: Stringify>(p: Pair<A, B>) String |
Render as (a, b); backs Stringify |
.to_string() |
fn<A: Stringify, B: Stringify, C: Stringify>(t: Triple<A, B, C>) String |
Render as (a, b, c); backs Stringify |
Fields are named first / second (and third for Triple):
use @std.tuple
use @std.tuple.{Pair, Triple}
fn pop<T>(stack: Vector<T>) Result<Pair<T, Vector<T>>, String> {
case stack.last() {
.Some(v) => .Ok(tuple.pair(v, stack.drop_last())),
.None => .Err("stack underflow"),
}
}
top := try pop(stack)
value := top.first
rest := top.second
t: Triple<Int, Int, Bool> = tuple.triple(1, 2, true)
"${t}" // "(1, 2, true)"
Sandboxed, low-level, manually-managed linear-memory buffers — Twinkle's second
mutate-in-place reference type alongside Cell. Buffer is an opt-in escape
hatch for workloads where GC-managed Vector<Byte> is too slow (e.g. byte codecs,
dense numeric arrays). Correctness — calling free, not using after free — is the
programmer's responsibility, like C. The only safety floor Wasm provides for
free is that all access stays within linear memory, so the worst case is corrupting
another buffer's bytes or trapping, never an escape from the sandbox. See
docs/plans/archive/buffer-linear-memory.md for the
design rationale.
Like @std.view and @std.tuple, two import lines give the full surface:
use @std.buffer // buffer.new(n), buf.view_i64(..)
use @std.buffer.{Buffer, U8View, I64View, F64View} // type names for annotations
The constructors are module-qualified (buffer.new, not Buffer.new).
Buffer type: pub type Buffer = .{ ptr: Int, size: Int } — a GC record
whose ptr is the linear-memory offset and size is the allocation size in
bytes. Both fields are public but raw; treat them as opaque outside @std.buffer.
Lifetime:
| Function | Signature | Description |
|---|---|---|
buffer.new(nbytes) |
fn(nbytes: Int) Buffer |
Allocate an uninitialized region. Traps on a negative or oversized request (linear-memory pointers are 32-bit) |
buffer.from_bytes(bytes) |
fn(bytes: Vector<Byte>) Buffer |
Allocate and copy a byte vector into linear memory |
buf.free() |
fn(b: Buffer) Void |
Release the region. Double-free corrupts allocator bookkeeping |
buf.len() |
fn(b: Buffer) Int |
Byte length of the region |
buf.to_bytes() |
fn(b: Buffer) Vector<Byte> |
Copy the bytes back out into an owned Vector<Byte> |
buf.to_string(off, len) |
fn(b: Buffer, off: Int, len: Int) String |
Decode len bytes at off directly into a String, skipping the per-byte Vector<Byte>/builder round-trip and UTF-8 validation. Caller guarantees the range is in-bounds and holds valid UTF-8 |
Raw byte-addressed accessors (byte off, little-endian, unaligned OK, unchecked against len — only the whole-memory bound traps):
| Function | Signature | Description |
|---|---|---|
buf.get_u8(off) |
fn(b: Buffer, off: Int) Byte |
Read one byte at byte offset off |
buf.set_u8(off, v) |
fn(b: Buffer, off: Int, v: Byte) Void |
Write one byte |
buf.set_byte(off, v) |
fn(b: Buffer, off: Int, v: Int) Void |
Write the low 8 bits of an Int directly, skipping the Byte wrapper — for hot codec loops that already hold the value as an Int |
buf.get_u32(off) |
fn(b: Buffer, off: Int) Int |
Read 4 bytes as an unsigned little-endian word, zero-extended to Int |
buf.set_u32(off, v) |
fn(b: Buffer, off: Int, v: Int) Void |
Write the low 32 bits of v as a little-endian word |
buf.get_i64(off) |
fn(b: Buffer, off: Int) Int |
Read 8 bytes as a little-endian i64 |
buf.set_i64(off, v) |
fn(b: Buffer, off: Int, v: Int) Void |
Write 8 bytes as a little-endian i64 |
buf.get_f64(off) |
fn(b: Buffer, off: Int) Float |
Read 8 bytes as a little-endian f64 |
buf.set_f64(off, v) |
fn(b: Buffer, off: Int, v: Float) Void |
Write 8 bytes as a little-endian f64 |
Element-indexed views (handles over a slice of the buffer; O(1) construction, no extra allocation; element index i, unchecked against count):
| Function | Signature | Description |
|---|---|---|
buf.view_u8(byte_off, count) |
fn(b: Buffer, byte_off: Int, count: Int) U8View |
Byte-element view over [byte_off, byte_off + count) |
buf.view_i64(byte_off, count) |
fn(b: Buffer, byte_off: Int, count: Int) I64View |
i64-element view; count is in elements, byte_off in bytes |
buf.view_f64(byte_off, count) |
fn(b: Buffer, byte_off: Int, count: Int) F64View |
f64-element view; count is in elements, byte_off in bytes |
Each view type (U8View, I64View, F64View) exposes the same interface:
| Method | Element type | Description |
|---|---|---|
v.len() |
— | Element count |
v.at(i) |
Int / Int / Float |
Element at index i (unchecked) |
v.set(i, x) |
— | Write element at index i in place (unchecked) |
v.slice(lo, hi) |
same view type | Sub-window [lo, hi) — O(1), shares backing, endpoints clamped |
v.iter() |
Iterator<Int> / Iterator<Int> / Iterator<Float> |
Iterate elements for for x in v.iter() |
U8View elements are raw byte values in the Int domain (0–255), avoiding the
Byte.from_int round-trip in hot loops. I64View elements are Int; F64View
elements are Float.
use @std.buffer
use @std.buffer.{Buffer, I64View}
// alloc, write, read back, free
buf := buffer.new(64)
v := buf.view_i64(0, 8) // 8-element i64 window over the first 64 bytes
i := 0
for i < 8 {
v.set(i, i * 10)
i = i + 1
}
total := 0
for x in v.iter() { total = total + x }
println("${total}") // 280
buf.free()
Manual-lifetime caveat. There is no automatic free, no use-after-free
detection, and no double-free guard. These are programmer responsibilities. Inside a
function or block body the idiomatic pattern is defer buf.free() at the allocation
site (a defer runs when its enclosing { } block exits); a top-level script scope
has no enclosing block, so there you must call buf.free() explicitly. A freed region
may be reused by the next buffer.new call (address-ordered free-list coalescing), so
use-after-free silently reads or corrupts the next allocation.