Skip to main content

core.base — Foundational types and protocols

Everything in core.base is loaded by the prelude — you do not need to mount it. It contains the types and protocols that every other module assumes.

This page enumerates every public item.

FileWhat's in it
maybe.vrMaybe<T>, MaybeIter<T>, collect_maybe, flatten_maybe
result.vrResult<T,E>, ResultIter<T>, Error
ordering.vrOrdering
ops.vrControlFlow<B,C>, Try, FromResidual, Never, Drop
protocols.vrthe operator and capability protocols (Eq, Ord, Hash, Clone, Copy, Default, Debug, Display, Send, Sync, Add..Shr, From/Into, Deref/AsRef, Numeric, …)
iterator.vrIterator protocol + 30+ adapters
cell.vrCell, RefCell, OnceCell, LazyCell, Ref, RefMut
memory.vrHeap<T>, Shared<T>, Weak<T>, Cow<T>, Pin<P>, ManuallyDrop<T>, MaybeUninit<T>
panic.vrpanic, assert, unreachable, todo, catch_unwind, PanicInfo
(uuid, ulid, nanoid, snowflake)moved
env.vrargs, var, home_dir, os_name, arch, exit
data.vrData (dynamic JSON-like value), parse_json
serde.vrSerialize, Deserialize, Serializer, Deserializer
error.vrStackFrame, Backtrace, ErrorProtocol, ErrorChain
log.vrLogLevel, Logger, LogRecord, trace/debug/info/warn/error
coinductive.vrcorecursive analysis (CorecursiveCall, check_productivity, bisimilar_up_to)
primitives.vrinherent methods on Int, Float, Bool, Char, Byte

Module status​

Each core.base.* module carries an explicit conformance status so you know what you can rely on today versus what is still in flight. The status is the truth-table over the module's public-API surface as exercised by core-tests/base/<module>/ under both verum test --interp (Tier 0 VBC interpreter) and verum test --aot (Tier 2 LLVM AOT). The same source file MUST pass both — interpreter/AOT divergence is itself a test failure (see core-tests/ for the discovery contract).

StatusMeaning
completeEverything stable requires, plus the coverage bar the conformance inventory sets for its top mark: algebraic laws pinned by property tests, cross-stdlib integration verified, and the module's audit findings landed or routed. A stable module graduates to complete when those land — the two are not synonyms.
stableEvery public method is conformance-tested under --interp and --aot. Algebraic laws are pinned by exhaustive or large-domain property tests. Cross-stdlib integration is verified. Safe to depend on in production.
partialSubset of the public API is conformance-tested and stable. The rest is exercised in regression_test.vr via @ignored tests pinning the specific defects that block coverage. The non-ignored API surface is safe; everything else is documented per-module under "Open defects".
regression-onlyModule is gated by upstream stdlib / language-level defects (function-id remap, archive-driven default-method dispatch, CBGR generation tracking on returned &Text, …). Public-API tests do not pass yet — only @ignored regressions exist to lock the bug shapes. Avoid in production until promoted.
undocumentedDocumentation in this reference is authoritative, but the module has not yet been routed through the core-tests/ conformance suite. The current page is a best-effort snapshot of the source; it may drift from runtime behaviour.
unverifiedThe conformance suite has not been run against this module, so nothing on this row is a measurement. Distinct from undocumented: the module IS routed into core-tests/, but no result has been recorded since the liveness check that began demanding one.
ModuleStatusConformance suite
maybe.vrpartialcore-tests/base/maybe — unit, property and integration suites all green under the interpreter. Maybe<T> implements Deref, and a bare None resolves against the expected type rather than against whichever parent was registered first.
result.vrpartialcore-tests/base/result — unit, property, integration, try-block and try-protocol suites all green under the interpreter.
ordering.vrpartialcore-tests/base/ordering — the dispatcher inlines Int.cmp, Float.cmp (NaN compares Equal), Bool.cmp (false < true) and Text.cmp. Comparisons through a reference — xs[0].cmp(&xs[1]), a.cmp(&record.f) — go through the same path as comparisons on values. f"{x}" routes through a user-defined implement Display for T when one exists.
ops.vrpartialcore-tests/base/ops — unit, property, integration and regression suites green under the interpreter, covering the whole ControlFlow<B, C> / Try / FromResidual / Never / Drop surface.
protocols.vrpartialcore-tests/base/protocols — unit, property, integration and protocol-agnostic suites green under the interpreter, covering the operator and capability protocols: Eq, Ord, Hash, Clone, Copy, Default, Debug, Display, From, Into and Add through Shr.
iterator.vrpartialcore-tests/base/iterator — the basic, property, integration, protocol-agnostic and regression suites are green under the interpreter; the unit suite has a documented tail of deferred cases. Transducers work end to end. Transducer.map, .filter, .filter_map, .take_while, .identity, .inspect and .compose2 all drive correctly through .transduce(…), and .take, .drop, .drop_while and .dedupe through .transduce_stateful(…) — measured over [1, 2, 3, 4], compose2(map(x*2), filter(>4)) answers 6 8. Range.reduce_with answers, and try_fold answers on every receiver tried. Deferred: reduce_with is refused on an iterator whose Iterator implementation lives in a module other than the one declaring the protocol — xs.iter(), xs.into_iter(), Deque.iter() and "ab".chars() report a type mismatch on the reducer's return, while (1..4) and every adapter (.map, .take, .rev, .filter) answer. Inserting any adapter above a refused receiver rescues it.
cell.vrpartialcore-tests/base/cell — the Cell family is stable, and dereferencing a RefCell guard yields the value: *rc.borrow() on a RefCell<Int> holding 42 answers 42. What does NOT yet work is reaching a method THROUGH the guard without writing the *: a call like guard.some_method() resolves against the guard type rather than the value's, so write the dereference explicitly until that is fixed. LazyCell now dereferences too — *cell computes the value on the first read and returns the cached one afterwards, with the initialiser called exactly once — so force(), which returns by value and therefore requires T: Clone, is no longer the only way in.
memory.vrpartialcore-tests/base/memory — Heap.new / Heap.into_inner / Heap.try_new / nested-heap round-trips stable in both interpreter and AOT. CBGR introspection surface (is_valid, is_allocated, is_freed, generation, epoch, header_generation, current_epoch, header_epoch, capabilities, header_size) exposed and round-trip-tested under cbgr_test.vr, where most of the suite does not yet pass: the failures are assertion failed: left != right (incl. test_heap_default, test_heap_cbgr_after_modification); root-cause hypothesis is T.default() primitive-protocol-default dispatch resolving to non-zero sentinel in interpreter. Stdlib companion fix landed in this branch: duplicate Heap.is_freed definition removed; language-level dup-method check added (see Open defects below).
panic.vrpartialcore-tests/base/panic — the conformance suite is green under the interpreter. 95 unit + 28 property + 11 integration + 9 regression pins (4 @ignore'd). Full panic / abort / exit / unreachable / todo + assert family (assert, assert_eq, assert_ne, assert_some, assert_none, assert_ok, assert_err, assert_approx_eq, assert_between, assert_is_sorted, assert_contains, assert_panics) + Location 3-field record + PanicInfo 2-field record + catch_unwind / CatchResult / PanicHook surface covered. Four @ignore'd pins for Formatter.write_int lenient-stub defect: the integer→bytes path references Text.from_utf8_unchecked which was private at text.vr:455; fix already staged in ulid commit (unsafe fn → public unsafe fn), activates on next precompiled-stdlib refresh. Every loc.fmt_debug(&mut formatter) / Display.fmt call eventually hits write_int and panics until then.
env.vrpartialcore-tests/base/env — the conformance suite is green under the interpreter. 31 unit + property + integration + 8 regression pins (3 @ignore'd). Full process args + env var + exit code + std helper surface (args / arg / args_count / args_os / var / var_opt / set_var / remove_var / VarError 2-variant / exit / exit_success / exit_failure / home_dir / user / path / temp_dir / shell / locale). Three @ignore'd pins for argv plumbing inconsistency: args_count() returns > 0 but arg(0) returns None — init_process_args not invoked or args_count/arg(i) read from different sources.
data.vrpartialcore-tests/base/data — the conformance suite is green under the interpreter. 60+ unit + property + integration + 10 regression pins (2 @ignore'd). Full Data 7-variant ADT (Null/Bool/Int/Float/Text/Array/Object) + DataBuilder (new / set / push / build) + parse_json + to_json + path traversal surface covered. Two @ignore'd pins: chained DataBuilder return-value defect — .new().set(...).set(...).build() followed by .len() mis-routes (same root family as base/log §A and base/mod §A — chained-builder type tracking through .build() return).
serde.vrpartialcore-tests/base/serde — the conformance suite is green under the interpreter. 5 unit + 6 property + 7 integration + 9 regression pins. Full SerdeError factory surface covered (new / unexpected_type / missing_field / unknown_field) + SerdeResult alias + protocol shape (Serialize / Deserialize / Serializer / Deserializer). Backend round-trip tests in core-tests/encoding/<backend>/ (cbor / hex / base64 / base32 / json_pointer / varint).
error.vrpartialcore-tests/base/error — 54/89. StackFrame.new / Eq / format stable; remaining failures span variant-payload drift + Display/Debug formatter dispatch via f"{...}".
log.vrpartialcore-tests/base/log — the conformance suite is green under the interpreter. 4 unit + 10 property + 8 integration + 9 regression pins (1 @ignore'd for §A). Full LogLevel ADT (Trace/Debug/Info/Warn/Error with severity 0..=4, canonical UPPERCASE names, total order, Default = Info), level_() free-fn constructors, LogRecord builders (.new / .simple / .with_), LogRecordBuilder fluent chain, NullLogger no-op backend. One @ignore'd pin for chained-builder return-value field-access defect: let rec = LogRecordBuilder.new().level(L).message(M).field(...).build(); rec.level.name() mis-resolves .level offset (the same field-index root as the subscript cases below, reached through a chained builder's return value). Workaround in integration_test: length-only assertions; typed-local pattern documented.
coinductive.vrpartialcore-tests/base/coinductive — the conformance suite is green under the interpreter. core/base/coinductive.vr extended with idiomatic method-form constructors and accessors (CorecursiveCall.is_guarded() / .new, ProductivityResult.is_productive() / .is_non_productive() / .unguarded_names(), Observation.new(label, payload), ObservationTrace.new / .from_list / .len / .is_empty / .get(i) / .prefix(n)) — all additive. integration_test.vr rewritten end-to-end (16 tests) against the actual API; regression_test.vr NEW with 6 pins. One @ignore'd pin (§E) for the field-access defect on t.steps[i].payload (the field-index resolver loses the Observation type through a List<Observation>[i] subscript; bind a typed local first). Method-form surface gated on next precompiled-stdlib refresh.
primitives.vrpartialcore-tests/base/primitives — Int.checked_add / checked_sub, the wrapping_* and saturating_* families are stable, as are the constants — Int.MAX, Int.BITS, Float.BITS, Float.NAN, Float.INFINITY, Float.PI, Float.MAX, Float.MIN, Float.EPSILON, Float.NEG_INFINITY. A method called on a cast receiver, (n as Byte).count_ones(), keeps the cast type's semantics rather than falling back to Int. Limitations: supported endian interpreter checks do not establish complete AOT conversion support; see byte conversion limitations. Width propagation through saturating_add on a sized integer and folding of a let-bound cast are also incomplete.
glob.vrpartialcore-tests/base/glob — the conformance suite is green under the interpreter. 13 unit + 5 property + 11 integration + 8 regression pins (0 @ignore'd). Pre-fix integration_test used a hallucinated GlobOptions.new().case_insensitive(true) API and called .matches on a Result<GlobPattern, _>; rewritten end-to-end with compile_with(pattern, case_sensitive: Bool) and compile(...).unwrap().matches_path(...). Full POSIX glob surface covered: * (within-segment) / ** (cross-segment) / ? (single-char) / [abc] / [a-z] / [!abc] / \* \? (escape) + case-sensitive vs case-insensitive compile.
coercion.vrcompletecore-tests/base/coercion — 1/2. IntCoercible / Indexable / RangeLike / TensorLike / BytewiseFfi marker protocols loaded from .vr; numeric_field_coercion blocked by field-index drift.
mod.vrpartialcore-tests/base/mod — the conformance suite is green under the interpreter. 16 unit + 9 property + 13 integration + 9 prelude + 14 regression pins (2 @ignore'd). Prelude re-exports (Maybe / Result / Ordering / List / Map / Set / Heap), type aliases (Byte / Bytes / TextResult), VERSION / VERSION_MAJOR / _MINOR / _PATCH constants, and Eq / Ord / Clone protocol accessibility all green. Two @ignore'd pins: (a) (1..=10).sum() returns 45 instead of 55 — the ..= inclusive range drops the upper bound (defect in either the desugar or Range.sum() default-method body). (b) take(&mut x) returns value correctly but doesn't reset x to T.default(). Both multi-day stdlib + VBC codegen fixes.
string_distance.vrpartialcore-tests/base/string_distance — the conformance suite is green under the interpreter. 27 unit + 9 property + 11 integration + 7 regression pins. Levenshtein full + bounded (Wagner-Fischer, rolling-window), Jaro and Jaro-Winkler similarity, closest_text / closest_n "did you mean ..." autocorrect API. All non-@ignore'd tests pass. Pre-fix every test file used a hallucinated API (&str.chars(), StringDistanceOptions, "x".bytes() → Bytes, [].to_bytes(), 2-arg closest_text); rewritten end-to-end using Text.as_bytes() and explicit max_distance. One @ignore'd pin (§G) for tuple-field access through List<(Text, Int)>[i].0 / .1 subscript losing element type (the same field-index root; clone and destructure through a typed local).
semver.vrpartialcore-tests/base/semver — unit, property, integration and regression suites green under the interpreter; the data surface — the five-field SemVer record, ordering per the specification's precedence rules, and the four-variant SemVerError — is fully covered.
semver_constraint.vrpartialcore-tests/base/semver_constraint — unit, property, integration and regression suites green under the interpreter. The whole constraint algebra is covered: the nine-variant SemVerConstraint (Any, Empty, Eq, Gt, Gte, Lt, Lte, And, Or), the convenience constructors (exact, caret, tilde, tilde_major, tilde_minor, x_range_major, x_range_minor), flatten-and-absorb composition through and / or, and MatchOptions in strict and allow-prerelease modes.
retry.vrpartialcore-tests/base/retry — unit, property, integration and regression suites green under the interpreter, covering the three-variant RetryBackoff, the RetryOptions<E> builder chain (.simple, .exponential, .linear, .with_should_retry) and first-attempt success through every entry point.

The status table is the runtime truth, not the file's lifecycle annotation — lifecycle: Lifecycle.Theorem("v0.1") is the spec lifecycle (what the contract promises), while the table above is the implementation lifecycle (what the runtime currently delivers). When the two diverge, the table is the source of truth for callers.

Method-dispatch surface​

The interpreter's method-dispatch flow combines several mechanisms — each contributes a class of behaviours that user code relies on. Knowing which mechanism fires for a given call site helps narrow runtime "method not found" diagnostics.

LayerWhat it dispatchesExample
Declared numeric bodyAn exactly resolved numeric method with a source body keeps its declaration's contract before primitive width dispatch.USize and UInt64 can declare different return types for the same method name.
Tier-0 inlineBuilt-in primitive methods (Int.cmp, Bool.lt, Float.partial_cmp, Text.eq, List.len, Map.get, ...) handled by the interpreter when no selected numeric source body takes precedence.42.cmp(&43) == Less
Receiver-type qualified lookup<TypeName>.<method> against the function table; uses the receiver heap-header's TypeId to recover the type name.MyError.message(&err)
Built-in TypeId → canonical-name fallbackWhen the receiver carries a hardcoded built-in TypeId (Range/List/Map/Set/Maybe/Result/...) but no TypeDescriptor exists in self.types, the dispatcher maps the TypeId to the canonical name (Range for TypeId(517) etc.) and retries the lookup.range.collect() where range has TypeId::RANGE
Receiver-type override (qualified mismatch)When codegen emits T.method but the runtime receiver is actually a different type (alias/inference drift), the dispatcher retries with <actual>.<bare>.r.is_err() codegen'd as Text.is_err on a Result receiver
Protocol-default-method fallbackWalks the receiver's TypeDescriptor.protocols; for each impl, tries the protocol's default body (<ProtocolName>.<method>).err.message() on a user MyError that implement ErrorProtocol without overriding message
Deref-protocol auto-derefWhen method dispatch fails on a heap receiver, walks the Deref chain (bounded depth 4) calling deref() to unwrap inner values, then retries dispatch by qualified name.refmut.push(item) on RefMut<List<T>>
Target-type-aware .into() rewriteAt codegen time, let x: T = expr.into() rewrites to T.from(expr) when T.from exists in the function table.let opt: Maybe<Int> = 42.into() → Some(42)
Unique bare-suffix matchLast resort when bare-name method is unique across the function table (single function ending in .<name>); skipped on ambiguity.unqualified it.next() resolving to a unique *.next

A numeric type's storage width does not choose its method declaration. For example, USize.to_be_bytes declares List<Byte>, while UInt64.to_be_bytes declares [Byte; 8]. A forward declaration without a source body preserves the existing builtin fallback. These declaration rules preserve each selected call's return contract. Source and decoded-bytecode LLVM/JIT controls cover both growable lists and packed fixed arrays; ordinary AOT support remains subject to the byte conversion limitations.

The table summarizes the dispatch mechanisms; their order also depends on the receiver and the call selected by the compiler. Protocol defaults and Deref forwarding handle unresolved calls. A "method not found" diagnostic can indicate that a required module was not loaded, a protocol implementation is missing, or the call's name disagrees with the archived declaration.


Maybe<T> — optional value​

type Maybe<T> is None | Some(T);

The Verum equivalent of Option. Used wherever a value may be absent.

Constructing​

let a: Maybe<Int> = Maybe.Some(42);
let b: Maybe<Int> = Maybe.None;

let parsed = "42".parse_int(); // returns Maybe<Int>

Inspecting​

m.is_some() -> Bool
m.is_none() -> Bool
m.contains(&value) -> Bool // requires T: Eq
m.is_some_and(f: fn(&T) -> Bool) -> Bool
m.is_none_or(f: fn(&T) -> Bool) -> Bool

Unwrapping​

m.unwrap() // panics if None
m.expect("must exist") // panics with message
m.unwrap_or(default)
m.unwrap_or_else(|| compute())
m.unwrap_or_default() // requires T: Default

Transforming​

m.map(|x| x + 1)Maybe
m.and_then(|x| if x > 0 { Some(x) } else { None })
m.or_else(|| compute_fallback())
m.map_or(default, |x| x + 1)
m.map_or_else(|| default(), |x| x + 1)
m.filter(|x| *x > 0)
m.flatten()Maybe<Maybe> -> Maybe (T: Default)

Combining​

m.and(other) // Maybe<U>: Some(_).and(x) = x; None.and(_) = None
m.or(other) // Maybe<T>: Some(x).or(_) = Some(x); None.or(y) = y
m.xor(other) // exclusive: Some/None or None/Some
m.zip(other) // Maybe<(T, U)>
m.zip_with(other, |a, b| a + b)

Result interop​

m.ok_or(error) // Maybe<T> -> Result<T, E>
m.ok_or_else(|| make_error())

Mutating​

m.take() // -> Maybe<T>, leaves *self = None
m.replace(value) // -> Maybe<T>, sets *self = Some(value)
m.insert(value) // -> &mut T
m.get_or_insert(value) // -> &mut T
m.get_or_insert_with(|| compute())
m.get_or_insert_default() // T: Default
m.take_if(|x| should_take(x))

Iteration​

for x in m.iter() { ... } // 0 or 1 element
for x in m.iter_mut() { ... }

Module-level helpers​

collect_maybe(iter) // Iterator<Maybe<T>> -> Maybe<List<T>> (all-or-nothing)
flatten_maybe(items) // &List<Maybe<T>> -> List<T> (skip None)
flatten_maybe_iter(iter) // Iterator<Maybe<T>> -> List<T>

? operator​

Outside a local try, ? propagates None from the nearest Maybe-returning function or closure:

fn first_word(text: &Text) -> Maybe<Text> {
let space = text.find(" ")?; // returns None early
Maybe.Some(text.slice(0, space))
}

Pitfall — accidental swallowing​

m.unwrap_or(default) discards the information that the value was absent. When the absence might indicate a bug, prefer expect (with a precise message) so the panic carries diagnostic value.


Result<T, E> — fallible value​

type Result<T, E> is Ok(T) | Err(E);

The canonical error type. By convention E is a dedicated error enum (type ParseError is …) or, for quick code, Text.

Constructing​

let r: Result<Int, Text> = Result.Ok(42);
let r: Result<Int, Text> = Result.Err("bad");

Inspecting​

r.is_ok() -> Bool
r.is_err() -> Bool
r.is_ok_and(|v| *v > 0)
r.is_err_and(|e| matches(e, Error.Timeout))

Conversion​

r.ok() // Result<T,E> -> Maybe<T>
r.err() // Result<T,E> -> Maybe<E>

Unwrapping​

r.unwrap()E: Debug; panics on Err
r.unwrap_err()T: Debug; panics on Ok
r.expect("must succeed")
r.expect_err("must fail")
r.unwrap_or(default)
r.unwrap_or_else(|e| recover(e))
r.unwrap_or_default()T: Default

Transforming​

r.map(|t| t + 1) // Result<U,E>
r.map_err(|e| ApiError.wrap(e)) // Result<T,F>
r.map_or(default, |t| t + 1)
r.map_or_else(|e| handle_err(e), |t| t + 1)
r.and_then(|t| further(t)) // flatmap on Ok
r.or_else(|e| recover(e)) // flatmap on Err
r.and(other) // (Ok(_), x) -> x; (Err(e), _) -> Err(e)
r.or(other)
r.flatten() // Result<Result<T,E>,E> -> Result<T,E>

Borrowing & inspection​

r.as_ref() // Result<T,E> -> Result<&T, &E>
r.as_mut() // -> Result<&mut T, &mut E>
r.inspect(|t| log(t)) // pass-through with side effect on Ok
r.inspect_err(|e| log(e)) // pass-through with side effect on Err

Iteration​

for v in r.iter() { ... } // 0 elements on Err, 1 on Ok
for v in r.iter_mut() { ... }

Error (lightweight catch-all)​

type Error is { message: Text };
let e = Error.new("something broke");

Implements Debug + Display + Describable + Eq + Clone. Useful when you do not yet have a typed error hierarchy. Replace with a dedicated error enum (type MyError is | NotFound | Timeout(Duration)) once the failure modes stabilise.

? operator​

In a function or closure returning Result<_, E>, ? unwraps an Ok or, outside a local try, returns an error early. An operand with error type F requires E: From<F> when conversion is needed; the returned error is E.from(error). Record-field and other expression type hints do not change the enclosing callable's return type.

fn load_config() -> Result<Config, Error> {
let bytes = fs.read("config.toml")?;
let text = Text.from_utf8(&bytes)?;
toml.parse(&text)
}

Pitfall — unwrap in libraries​

unwrap is fine in tests and prototypes. In library code, prefer ? and let the caller decide. expect("invariant: …") is acceptable when you can articulate why panic is the only sane response.


Ordering​

type Ordering is Less | Equal | Greater;

The result of cmp. Total ordering primitives.

o.is_less() / o.is_equal() / o.is_greater()
o.is_le() / o.is_ge()
o.reverse() // Less <-> Greater
o.then(other) // lexicographic chain
o.then_with(|| compute()) // lazy chain
o.to_int() // -1 / 0 / 1
Ordering.from_int(n) // parse from -1/0/1

Lexicographic comparison idiom:

fn cmp(a: &Person, b: &Person) -> Ordering {
a.last.cmp(&b.last)
.then_with(|| a.first.cmp(&b.first))
.then_with(|| a.age.cmp(&b.age))
}

ControlFlow<B, C> and Try​

type ControlFlow<B, C> is Continue(C) | Break(B);

The protocol behind ?. You rarely write it directly, but it powers custom early-return types:

type Try is protocol {
type Output;
type Residual;
fn from_output(output: Output) -> Self;
fn branch(self) -> ControlFlow<Residual, Output>;
};

type FromResidual<R> is protocol {
fn from_residual(residual: R) -> Self;
};

Try.branch separates a successful output from a failure residual. For early return, the nearest enclosing function or closure's return type must provide the matching FromResidual conversion. Implementing Try for an operand alone does not provide that conversion.

The standard library declares these failure conversions in maybe.vr and result.vr:

OperandCallable return typePropagated failureRequired bound
Maybe<_>Maybe<_>Maybe.None—
Result<_, E>Result<_, E>Result.Err(error)—
Result<_, F>Result<_, E>Result.Err(E.from(error))E: From<F>
Maybe<_>Result<_, E>Result.Err(E.default())E: Default
Result<_, E>Maybe<_>Maybe.None; the error is discarded—

A try in the same callable provides a local recovery boundary. Nested closures keep their own return type and handlers; they do not inherit the enclosing callable's recovery scope. See nested callables and recovery.

Never (alias !) is the bottom type; it can be coerced to any type. Returned by panic, exit, infinite loops, etc.


Operator protocols​

Implementing these makes your type work with the corresponding syntax. From protocols.vr:

ProtocolSyntaxMethods
PartialEq==, !=eq(&self, other: &Self) -> Bool
Eq(marker)extends PartialEq
PartialOrd<, <=, >, >=partial_cmp(&self, other: &Self) -> Maybe<Ordering>
Ord(sortable)cmp(&self, other: &Self) -> Ordering; min, max, clamp
Hashhashinghash<H: Hasher>(&self, h: &mut H)
Clone.clone()clone(&self) -> Self, clone_from(&mut self, other: &Self)
Copyimplicit copyextends Clone; marker only
DefaultT.default()default() -> Self
Debug{:?}fmt_debug(&self, f: &mut Formatter) -> FmtResult
Display{}fmt(&self, f: &mut Formatter) -> FmtResult
Dropscope exitdrop(&mut self)
Add<Rhs>a + badd(self, rhs: Rhs) -> Self.Output
Sub, Mul, Div, Rem, Negarithmeticanalogous
BitAnd, BitOr, BitXor, Not, Shl, Shrbitwiseanalogous
AddAssign..ShrAssigna += b etc.in-place compound
Index<Idx>xs[i]index(&self, i: Idx) -> &Self.Output
IndexMut<Idx>xs[i] = …index_mut(&mut self, i: Idx) -> &mut Self.Output
Derefauto-derefderef(&self) -> &Self.Target
DerefMutmutable auto-derefderef_mut(&mut self) -> &mut Self.Target
From<T>T.from(x)from(x: T) -> Self
Into<T>x.into()into(self) -> T
TryFrom<T> / TryInto<T>fallible variantswith Error associated type
AsRef<T>&Self -> &Tas_ref(&self) -> &T
AsMut<T>&mut Self -> &mut Tas_mut(&mut self) -> &mut T
Borrow<B> / BorrowMut<B>hash-key borrowingborrow(&self) -> &B

Numeric protocols​

type Zero is protocol { fn zero() -> Self; fn is_zero(&self) -> Bool; }
type One is protocol { fn one() -> Self; fn is_one(&self) -> Bool; }
type Numeric is protocol; // implemented by all primitive numerics
type SignedNumeric is protocol extends Numeric;
type Integer is protocol extends Atomic;
type SignedInteger is protocol extends Integer;
type Atomic is protocol; // safe for atomic ops

Capability protocols​

type Sized is protocol; // statically sized; auto for almost all types
type Send is protocol; // safe to move across threads
type Sync is protocol; // safe to share `&Self` across threads
type Unpin is protocol; // not structurally pinned
type Any is protocol { fn type_id(&self) -> TypeId; }

Send and Sync are auto-derived; opt out with !Send / !Sync in generic bounds (fn f<T: Send + !Sync>(x: T)).

Error protocols​

type Describable is protocol { fn description(&self) -> Text; }
type ErrorSource is protocol extends Describable {
fn source(&self) -> Maybe<&dyn ErrorSource>;
}
type ErrorProtocol is protocol extends ErrorSource {
fn message(&self) -> Text;
fn backtrace(&self) -> Maybe<Backtrace>;
}
type FromStr is protocol { fn from_str(s: &Text) -> Result<Self, ParseError>; }
type ToString is protocol { fn to_string(&self) -> Text; }

Implementations the stdlib provides​

TraitFor
Numericevery primitive integer + every Float*
Integerevery primitive integer
SignedIntegerInt8..Int128, Int, ISize
Atomicevery primitive integer + Bool
HasherDefaultHasher (FxHash-based, wrapping_mul core)

The Display + Debug + Eq triple — error-type contract​

Every public stdlib type whose name ends in Error (and every state-machine / status / outcome type whose values cross module boundaries) implements all three of Display, Debug, and Eq. The contract is enforced across ~280 stdlib error types covering async, base, collections, compress, database/{common,sqlite,mysql, postgres}, encoding, intrinsics, io, mem, mesh/{k8s,xds}, meta, net/{dns,h3,http,http2,http3,proxy,quic,tls,tls13,url,websocket}, runtime, security, sys/{common,linux,darwin,windows,signal}, text, time.

Why all three:

  • Display — user-facing message routed through print(...), panic(...), error-renderer, log lines, network-protocol error bodies. Lowercase / sentence-case message; no internal symbol names. e.g. "connection refused: 127.0.0.1:5432".

  • Debug — structured-log payload routed through core.base.log.{trace,debug,info,warn,error}, f"{:?}", snapshot serialisers, gdb-friendly dumps. Symbol-form repr that round-trips back to the literal source constructor. e.g. "DbError.ConnectionRefused { host: \"127.0.0.1\", port: 5432 }".

  • Eq — pattern-test discipline: assert_eq(err, ExpectedKind) in tests, if err == NetworkError.Timeout { … } in retry logic, Set<MyError> in dedup workflows.

Recipe by error-type shape (the four canonical patterns):

// Pattern 1 — All-unit variants. Tag-int helper avoids the
// path-type confusion that bites pair-form match on single-variant
// types.
fn my_err_tag(e: &MyError) -> Int {
match e {
Foo => 0,
Bar => 1,
Baz => 2,
}
}
implement Eq for MyError {
fn eq(&self, other: &MyError) -> Bool {
my_err_tag(self) == my_err_tag(other)
}
}

// Pattern 2 — Mixed unit + payload variants. Direct match-pair
// with field renaming on payload arms; cross-variant fall-through.
implement Eq for MyError {
fn eq(&self, other: &MyError) -> Bool {
match (self, other) {
(MyError.Timeout, MyError.Timeout) => true,
(MyError.Other(a), MyError.Other(b)) => a == b,
_ => false,
}
}
}

// Pattern 3 — Single-variant unit type. Trivial Eq; deliberately
// avoids `(SoleVariant, SoleVariant) => true` form that the type
// checker reads as a path-type lookup for the bare variant name.
implement Eq for MyError {
fn eq(&self, _other: &MyError) -> Bool { true }
}

// Pattern 4 — Record-form error with multiple fields. Compare
// every field; structural equality.
implement Eq for MyError {
fn eq(&self, other: &MyError) -> Bool {
self.code == other.code
&& self.message == other.message
&& self.span == other.span
}
}

When not to add Eq:

  • The error wraps a protocol object (Heap<dyn Error>, List<Heap<dyn Error>>). The Error protocol is intentionally not Eq-extending — comparing two protocol objects via .message() text would be an oracle leak masquerading as structural equality. Defer Eq with an inline rationale comment.

  • The error carries a payload of type Span (or any other type whose Eq impl is unstable / position-dependent) and structural span-comparison would surface false negatives across re-parses. Compare the meaningful fields only and document the elision.


Iterator and adapters​

type Iterator is protocol {
type Item;
fn next(&mut self) -> Maybe<Self.Item>;
fn size_hint(&self) -> (Int, Maybe<Int>);
// ... 60+ default methods
}

Consuming methods (terminal)​

.count() // -> Int
.last() // -> Maybe<Item>
.nth(n) // -> Maybe<Item>
.advance_by(n) // -> Result<(), Int>
.collect<C>() // -> any C: FromIterator<Item>
.fold(init, |acc, x| ...) // -> B
.reduce(|a, b| max(a,b)) // -> Maybe<Item>
.try_fold(init, |acc, x| ...)
.scan(state, |st, x| ...)
.all(|x| pred(x)) // -> Bool (short-circuits on false)
.any(|x| pred(x)) // -> Bool (short-circuits on true)
.find(|x| pred(x)) // -> Maybe<Item>
.find_map(|x| ...) // -> Maybe<U>
.position(|x| pred(x)) // -> Maybe<Int>
.sum() // -> Item: Add + Zero
.product() // -> Item: Mul + One
.min() / .max() // -> Maybe<Item> (Item: Ord)
.min_by_key(|x| key(x)) / .max_by_key
.min_by(|a,b| cmp) / .max_by
warning

Known limitation: min_by and max_by crash the compiler. This call —

l.iter().min_by(|a, b| a.cmp(b))

— ends the build with

verum: internal compiler error — a crash report has been saved.
(panic: Expected int, got Some(0))

The keyed forms are fine. Run on the same list:

mount core.collections.{List};

fn main() {
let l: List<Int> = List.from([3, 1, 2]);
match l.iter().min_by_key(|x| *x) {
Maybe.Some(v) => print(f"min={v}"),
Maybe.None => print("min=none"),
}
match l.iter().max_by_key(|x| *x) {
Maybe.Some(v) => print(f"max={v}"),
Maybe.None => print("max=none"),
}
}
min=1
max=3

Use those until this note goes away; the difference is the comparator's shape, not the iterator.

.min_max() // -> Maybe<(Item, Item)>

Adapter methods (lazy — return new iterators)​

.map(|x| f(x)) // MappedIter
.filter(|x| pred(x)) // FilterIter
.filter_map(|x| ...) // FilterMapIter
.flat_map(|x| produce_seq(x)) // FlatMapIter
.flatten() // FlattenIter — Iter<Iter<T>> -> Iter<T>
.take(n) // TakeIter
.skip(n) // SkipIter
.take_while(|x| pred(x)) // TakeWhileIter
.skip_while(|x| pred(x)) // SkipWhileIter
.chain(other_iter) // ChainIter
.zip(other_iter) // ZipIter
.enumerate() // EnumerateIter — yields (Int, Item)
.peekable() // PeekableIter — adds .peek()
.dedup() // DedupIter — removes consecutive equals
.interleave(other_iter) // InterleaveIter
.step_by(stride) // StepByIter
.inspect(|x| log(x)) // InspectIter — pass-through with side effect
.fuse() // FuseIter — None stays None
.cycle() // CycleIter — repeats forever
.cloned() / .copied() // Iter<&T> -> Iter<T>
.chunks(n) // ChunksIter — windows of n
.windows(n) // WindowsIter — sliding window
.intersperse(separator) // IntersperseIter
.intersperse_with(|| sep()) // IntersperseWithIter
.pairwise() // PairwiseIter — yields (Item, Item)
.zip_longest(other) // ZipLongestIter
.map_while(|x| ...) // MapWhileIter — stops on None
.by_ref() // ByRef — iterate without consuming

Idiomatic chains​

// sum of squares of odd numbers in 0..100
let s: Int = (0..100).filter(|x| x % 2 == 1).map(|x| x * x).sum();

// first prime > 1000
let p = (1001..).find(|n| is_prime(n));

// dedupe a sorted list and join with commas
let csv: Text = sorted.iter().dedup().map(|x| x.to_string())
.intersperse(",".to_string()).collect();

// produce a Map<Int, List<Text>> grouped by length
let by_len: Map<Int, List<Text>> = words.iter()
.fold(Map.new(), |mut m, w| {
m.entry(w.len()).or_insert_with(List.new).push(w.clone());
m
});

Pitfall — re-using a consumed iterator​

Iterators are single-use unless they implement Clone. Use .by_ref() when you want to consume only part of an iterator and keep iterating later.


Cells (interior mutability)​

Cell<T> — copy-based, !Sync​

let c = Cell.new(0);
c.set(42);
c.get(); // requires T: Clone
c.replace(100); // returns old value
c.take(); // returns value, leaves Default (T: Default)
c.update(|v| v + 1); // apply function in-place

RefCell<T> — runtime-borrow-checked, !Sync​

let rc = RefCell.new(List.new());
{
let mut w = rc.borrow_mut(); // panics if any borrow active
w.push(1);
}
{
let r = rc.borrow(); // panics if mutable borrow active
print(f"{r.len()}");
}

// Non-panicking variants:
match rc.try_borrow_mut() {
Result.Ok(mut w) => { w.push(2); }
Result.Err(_) => log("contended"),
}

OnceCell<T> — write-once​

let cfg: OnceCell<Config> = OnceCell.new();
cfg.set(load_config()).unwrap();
let value = cfg.get_or_init(|| load_config());

LazyCell<T> — lazy computation​

let computed: LazyCell<HeavyResult> = LazyCell.new(|| compute_once());
let result = computed.force(); // computes on first call, caches

Protocol implementations​

TypeEqOrdHashCloneDefaultDebugDisplay
Cell<T>T: Clone+EqT: Clone+Ord— (interior mutability)T: CloneT: Clone+DefaultT: Clone+DebugT: Clone+Display
RefCell<T>T: Eq—— (interior mutability)T: CloneT: DefaultT: DebugT: Display
OnceCell<T>T: Clone+Eq—T: Clone+HashT: CloneyesT: Clone+DebugT: Clone+Display

Cell and RefCell deliberately omit Hash — interior mutability would let the contained value (and thus the hash) change between insertions and lookups in a Map<Cell<T>, V>, breaking the map invariant. Same policy as Rust's Cell<T>: !Hash / RefCell<T>: !Hash. OnceCell<T> is write-once: once set() succeeds, both Eq and Hash see the same value forever, so the Hash–Eq invariant holds and Map<OnceCell<T>, V> is sound.

Display for Cell / OnceCell delegates transparently to the inner value's Display; the wrapper is invisible to user-facing rendering. Display for RefCell does the same when no mutable borrow is active and falls back to the sentinel <borrowed> otherwise (because handing out a shared borrow for fmt concurrently with a mutable borrow would violate the dynamic borrow-check; printing the sentinel is preferable to panicking inside formatter machinery).

For thread-safe equivalents, see sync (AtomicCell, Mutex, RwLock, OnceLock).


Heap, Shared, Cow, Pin​

TypeSemantics
Heap<T>unique heap allocation; CBGR-tracked
Shared<T>atomically ref-counted; Send + Sync if T: Send + Sync
Weak<T>non-owning ref to a Shared<T> (breaks cycles)
Cow<T>clone-on-write — borrowed &T until you call .to_mut()
Pin<P>structurally pinned (used for self-referential futures)
ManuallyDrop<T>wraps T to prevent automatic drop
MaybeUninit<T>wraps possibly-uninit memory; safe initialisation

Heap<T>​

Heap<T> is the unique-ownership heap allocation. Each Heap carries a 16-byte (ptr, generation: UInt32, epoch: UInt16) triple; deref-time CBGR validation re-reads the allocation header's atomic generation and epoch and panics on mismatch — that is how use-after-free is detected at Tier 0 with ~15ns overhead. Tier 1 elides the check when the compiler proves the reference cannot outlive the allocation.

Construction

Heap.new(value: T) -> Heap<T> // panics on OOM
Heap.new_default() -> Heap<T> // T: Default
Heap.new_zeroed() -> Heap<T> // bit-pattern 0
Heap.try_new(value: T) -> Result<Heap<T>, AllocError> // non-panicking

Deref (CBGR-checked)

h.as_ref() -> &T
h.as_mut() -> &mut T
*h // implicit deref via Deref protocol — same CBGR check

Move / leak / round-trip

h.into_inner() -> T // free header, return value
h.into_raw() -> &unsafe T // intentional leak; pair with from_raw
unsafe Heap.from_raw(p: &unsafe T) -> Heap<T>
h.leak() -> &mut T // 'static-lifetime reference

CBGR introspection — read the underlying allocation header directly, skipping the cached (generation, epoch) triple stored on the Heap. Use these for diagnostics and conformance tests; in production code the implicit deref handles validation for you.

h.generation() -> UInt32 // cached value from construction
h.epoch() -> UInt16 // cached value from construction
h.is_valid() -> Bool // both gen + epoch agree with header
h.is_allocated() -> Bool // header.generation != 0
h.is_freed() -> Bool // header.generation != h.generation
h.header_generation() -> UInt32 // re-read header atomically (Acquire)
h.current_epoch() -> UInt16 // re-read epoch from header
h.header_epoch() -> UInt32 // alias of current_epoch (UInt32 shape)
h.capabilities() -> UInt16 // header capability bits
h.header_size() -> Int // CBGR header byte size — currently 32

All introspection methods use atomic loads with ORDERING_ACQUIRE, so they synchronise correctly across threads. header_size() returns the constant 32; if the allocator's AllocationHeader ever changes size, this constant must update in lockstep with crates/verum_common/src/layout.rs::ALLOCATION_HEADER_SIZE (which is 32 today, and is the value header_size() reports) — the inline - 32 offset is repeated at six sites inside core/base/memory.vr and is tracked as a hoist-to-constant audit item.

Protocol implementations

ProtocolBoundBehaviour
Deref / DerefMutalways*h calls as_ref / as_mut (CBGR-checked)
Dropalwaysruns drop_in_place on *self.ptr, then cbgr_dealloc (header generation increment for CBGR invalidation)
CloneT: CloneHeap.new((**self).clone()) — independent allocation
DebugT: Debugwraps as Heap(<inner>)
DisplayT: Displaytransparent — delegates to T.fmt, wrapper invisible
EqT: Eq**self == **other
OrdT: Ord(**self).cmp(&**other)
HashT: Hash(**self).hash(hasher)
DefaultT: DefaultHeap.new_default()

Shared<T> and Weak<T>​

// `new` is the only associated function here; the rest are METHODS.
Shared.new(value) s.clone() s.downgrade() -> Weak<T>
weak.upgrade() -> Maybe<Shared<T>>
s.strong_count() / s.weak_count()

Weak<T> carries identity-based Eq and Hash — two Weak<T> compare equal iff they were produced from the same Shared<T> allocation (matching pointer + generation + epoch). This is the only sound equality for weak handles: comparing inner values would require upgrade-then-deref, but the upgrade can fail mid-comparison if another thread drops the last Shared, breaking transitivity. Identity equality lets Map<Weak<T>, V> work (cycle-breaking caches, weak-keyed observer registries). Debug on Weak<T> renders Weak { gen: N, epoch: M, alive } or … dropped } for post-mortem dumps without leaking raw pointers.

Cow<T>​

let c: Cow<List<Int>> = Cow.Borrowed(&xs);
let owned: &mut List<Int> = c.to_mut(); // clones if Borrowed

Raw pointer helpers​

ptr_read(p) ptr_write(p, value) // unsafe
drop_in_place(p) forget(value) // mem leaks
is_null(p) null_ptr<T>()
ptr_offset(p, n)

Protocol implementations — Display is transparent, Debug is wrapped​

Smart-pointer types in core.base.memory follow a deliberate asymmetry between Display and Debug:

TypeDisplay<T>Debug<T>
Heap<T: Display>(**self).fmt(f) — invisible wrapperHeap(<inner>)
Shared<T: Display>(**self).fmt(f) — invisible wrapperShared(<inner>)
Cow<T: Clone + Display>self.as_ref().fmt(f) — invisibleBorrowed(<inner>) / Owned(<inner>)

Rationale:

  • Display is for user-facing rendering — print(x), f"…{x}…", network-protocol error bodies, log lines. Box-style pointers are conceptually invisible at this layer: print(Heap.new(42)) should produce 42, not Heap(42).

  • Debug is for structured-log payload — core.base.log.{trace,debug,info,warn,error}, f"…{x:?}…", gdb-style dumps, snapshot serialisers. Here the wrapper IS information: knowing Heap("hello") vs the raw "hello" distinguishes "owned-on-heap" from a stack value during incident analysis.

This split mirrors Rust's Display for Box<T> / Debug for Box<T> convention and aligns with the broader Display + Debug + Eq triple contract above — every public stdlib pointer type now provides Display + Debug + Eq + Hash + Clone + Default + Ord (gated by the inner type's bounds).

For full memory primitives see mem.


panic and assertions​

Panicking​

panic("invariant violated") -> !
panic_at("oops", "src/foo.vr", 12, 4) -> !
abort() -> ! // immediate; no unwinding
exit(code) -> !

Assertions​

Core assertions — always on, panic on failure:

assert(cond, "message")
assert_eq(left, right, "message") // T: Eq + Debug
assert_ne(left, right, "message")
assert_some(maybe, "expected Some")
assert_none(maybe, "expected None")
assert_ok(result, "expected Ok")
assert_err(result, "expected Err")

Extended assertions (added for reference-quality testing — see Tooling → Testing):

// Float comparison with tolerance — use this INSTEAD of assert_eq
// on any Float-typed value; direct IEEE-754 equality is almost never
// what you want after arithmetic.
assert_approx_eq(left: Float, right: Float, tolerance: Float = 1e-9, msg: Text)

// Inclusive-range check: fails if v < lo or v > hi.
assert_between<T: Ord>(v: T, lo: T, hi: T, msg: Text)

// Sorted-ascending check over &List<T>. O(n).
assert_is_sorted<T: Ord>(list: &List<T>, msg: Text)

// Membership check. O(n) linear scan.
assert_contains<T: Eq>(list: &List<T>, needle: &T, msg: Text)

// Expects the closure to panic. Succeeds iff it does; fails the
// assertion if the closure returns normally. Implemented via
// catch_unwind.
assert_panics<T>(f: fn() -> T, msg: Text)

Debug-only variants — stripped in release builds:

debug_assert(cond, "msg")
debug_assert_eq(a, b, "msg")
debug_assert_ne(a, b, "msg")

Markers​

unreachable("dead branch") -> !
unreachable_unchecked("dead in release; UB hint") -> !
unimplemented("phase 2") -> !
todo("write me") -> !

Catch panics​

catch_unwind(|| risky()) -> Result<T, PanicInfo>
resume_unwind(panic_info) -> !

Compile-time helpers​

@file()
@line()
@column()
@function()
@module()
@current_location()

PanicInfo carries message: Text, optional Location { file, line, column }. set_panic_handler(hook) installs a global hook (fn(&PanicInfo)).

dbg — print-and-pass-through​

let total = dbg(items.iter().filter(|x| x.is_active()).count());
// stderr: [src/foo.vr:42] items.iter().filter(...).count() = 17

env — process environment​

CLI arguments​

args() -> List<Text> // all argv
arg(i) -> Maybe<Text>
args_count() -> Int
args_os() -> Args // iterator

Environment variables​

var(key) -> Result<Text, VarError>
var_opt(key) -> Maybe<Text>
set_var(key, value)
remove_var(key)
vars() -> Vars // iterator over (Text, Text)
vars_list() -> List<(Text, Text)>

VarError is NotPresent | NotUnicode(List<Byte>).

Standard locations​

home_dir() -> Maybe<Text> // HOME or USERPROFILE
user() -> Maybe<Text> // USER or USERNAME
path() -> Maybe<Text> // PATH
shell() -> Maybe<Text> // SHELL or COMSPEC
locale() -> Maybe<Text> // LANG / LC_*
temp_dir() -> Text // TMPDIR/TMP/TEMP or /tmp

Platform info​

os_name() -> Text // "macos", "linux", "windows"
arch() -> Text // "aarch64", "x86_64", ...
os_family() -> Text // "unix" or "windows"

Exit​

exit(code) -> !
exit_success() -> ! // 0
exit_failure() -> ! // 1

Path utilities​

split_paths(path_str) -> List<Text> // splits PATH-style string

Data — dynamic typed value​

When you genuinely don't know the schema (e.g., reading arbitrary JSON configuration), Data is the JSON-like dynamic value:

type Data is
| Null
| Bool(Bool)
| Int(Int)
| Float(Float)
| Text(Text)
| Array(List<Data>)
| Object(Map<Text, Data>);

Construction & parsing​

Data.null()
Data.from_int(42)
Data.from_text("hi")
Data.empty_object()
Data.empty_array()
parse_json(input) -> Result<Data, DataError>

Type predicates and accessors​

d.is_null() d.is_bool() d.is_int() d.is_float() d.is_text()
d.is_array() d.is_object() d.is_number()
d.as_bool() d.as_int() d.as_float() d.as_text()
d.as_array() d.as_object()
d.as_array_mut() d.as_object_mut()
d.type_name() -> Text

Object / array operations​

d.get(key) d.get_mut(key) d.contains_key(key)
d.set(key, value) -> Result<(), DataError>
d.remove(key) -> Maybe<Data>
d.at(index) d.at_mut(index)
d.push(value) -> Result<(), DataError>
d.pop() -> Maybe<Data>
d.len() -> Int d.is_empty() -> Bool
d.keys() -> Maybe<List<Text>> // None unless this is an Object
d.values() -> Maybe<List<Data>> // None unless this is an Object

Path access, merging, output​

d.path("user.address.city") -> Maybe<&Data>
d.merge(&other) d.deep_merge(&other)
d.to_json() d.to_json_pretty()
d.to_string() d.to_number()

DataError​

type DataError is
| TypeMismatch { expected: Text, actual: Text }
| KeyNotFound { key: Text }
| IndexOutOfBounds { index: Int, length: Int }
| ParseError { message: Text }
| InvalidCast { from: Text, to: Text };

For typed schemas, prefer dedicated record types with @derive(Serialize, Deserialize) — see serde.vr below.


Serialisation — Serialize / Deserialize​

type Serialize is protocol {
fn serialize<S: Serializer>(&self, s: &mut S) -> Result<S.Ok, S.Error>;
}
type Deserialize is protocol {
fn deserialize<D: Deserializer>(d: &mut D) -> Result<Self, D.Error>;
}

The format is supplied by an implementation of the dual Serializer / Deserializer protocols (one per format: JSON, TOML, YAML, …). Most user types just @derive(Serialize, Deserialize) and let an external cog (e.g. toml, serde_json-equivalent) drive the format.

Helper builders:

ListSerializer.serialize_element(&value)
MapSerializer.serialize_key(&k)
MapSerializer.serialize_value(&v)
RecordSerializer.serialize_field(name, &value)

SerdeError is the common error: constructors include unexpected_type(expected, found), missing_field(name), unknown_field(name).


error — backtraces and chains​

type StackFrame is { function: Text, file: Text, line: Int, column: Int };
type Backtrace is { frames: List<StackFrame> };
Backtrace.capture() -> Backtracecurrent stack (best-effort)
Backtrace.from_frames(frames) -> Backtrace
bt.frames(), bt.is_empty(), bt.len()

ErrorProtocol​

type ErrorProtocol is protocol extends ErrorSource {
fn message(&self) -> Text;
fn backtrace(&self) -> Maybe<Backtrace>;
}

Chain rendering​

format_error_chain(&error) -> Text // walks .source() and prints all
match result {
Result.Err(e) => eprint(format_error_chain(&e)),
Result.Ok(_) => (),
}

log — structured logging protocol​

type LogLevel is Trace | Debug | Info | Warn | Error;

type Logger is protocol {
fn log(&self, level: LogLevel, message: &Text);
fn is_enabled(&self, level: LogLevel) -> Bool;
}

type LogRecord is {
level: LogLevel,
message: Text,
fields: List<(Text, Text)>,
module_path: Maybe<Text>,
file: Maybe<Text>,
line: Maybe<Int>,
};

type LogRecordBuilder is { /* opaque */ };

Level constructors (free functions)​

For mount import sites that prefer free-function style over LogLevel.Trace:

mount core.base.log.{
LogLevel, LogRecord, LogRecordBuilder,
level_trace, level_debug, level_info, level_warn, level_error,
};

let l: LogLevel = level_warn();

LogLevel itself implements Eq, Ord, Clone, Copy, Hash, Display, Debug, Default (defaults to Info); severity() → 0..4 and name() → "TRACE".."ERROR" are the workhorse accessors.

LogRecord construction​

Chainable builder methods (each returns a new record):

LogRecord.new(level, message, fields)3-arg canonical
LogRecord.simple(level, message)shortcut, fields = []
rec.with_field(key, value)
rec.with_module_path(path)
rec.with_file(file)
rec.with_line(line)

LogRecordBuilder — imperative builder​

For sites where a record is assembled across control-flow paths (e.g. attaching fields conditionally before finalising):

let rec = LogRecordBuilder.new()
.level(level_warn())
.message("disk full".to_text())
.field("disk".to_text(), "/var".to_text())
.field("free_gb".to_text(), "0".to_text())
.module_path("daemon::disk".to_text())
.file("disk.vr".to_text())
.line(142)
.build();

Defaults: Info / empty message / no fields / no source location.

Convenience functions (require Logger in context)​

fn handle(req: Request) using [Logger] {
info(&"received request");
debug(&f"path = {req.path}");
if !req.is_authenticated() {
warn(&f"unauthorised access from {req.remote_addr}");
}
error(&f"unexpected: {err}");
}

Backends​

NullLogger (no-op, useful in tests). Real backends are in user code or third-party cogs (file logger, journald, syslog, …). To wire a backend, implement Logger for MyBackend { … } and provide it via provide MyBackend.new() as Logger { … } — the same context-system plumbing covered in stdlib/context.


coinductive — productivity & bisimulation analysis​

For corecursive function bodies. Most users will not import this directly; it backs the cofix modifier's productivity check.

type CorecursiveCall is { callee: Text, guard_depth: Int{>= 0} };
type ProductivityResult is Productive | NonProductive { unguarded: List<Text> };

corec_call(callee, depth) check_productivity(calls)
is_guarded(call)
observation(label, payload) trace(steps)
trace_prefix(t, n)
observations_equal(a, b)
bisimilar_up_to(left, right, depth) // BisimulationResult

Primitive methods (primitives.vr)​

Inherent methods on the built-in numeric / character types. Selected highlights — see source for full list.

Int (64-bit signed)​

constants: Int.MIN Int.MAX Int.BITS
predicates: is_positive, is_negative, is_zero, is_power_of_two
arithmetic: abs, signum, pow(n), min, max, clamp
checked: checked_add, checked_sub, checked_mul, checked_div, ...
wrapping: wrapping_add, wrapping_sub, wrapping_mul, ...
saturating: saturating_add, saturating_sub, saturating_mul
overflowing: overflowing_add (-> (T, Bool)), ...
bits: leading_zeros, trailing_zeros, count_ones, count_zeros,
swap_bytes, reverse_bits, rotate_left, rotate_right
bytes: to_le_bytes, to_be_bytes, from_le_bytes, from_be_bytes
conversion: to_float, to_binary, to_hex, to_octal, to_text, parse_int
euclidean: div_euclid, rem_euclid
range: in_range, ilog2, ilog10, abs_diff

Float (64-bit)​

constants: Float.MIN Float.MAX Float.MIN_POSITIVE Float.NAN
Float.INFINITY Float.NEG_INFINITY
classify: is_nan, is_infinite, is_finite, is_normal,
is_sign_positive, is_sign_negative
arithmetic: abs, signum, trunc, fract, round, ceil, floor,
sqrt, cbrt, pow(n), exp, log, log10, log2
trig: sin, cos, tan, asin, acos, atan, atan2
hyper: sinh, cosh, tanh, asinh, acosh, atanh
ops: min, max, clamp, copysign, fma, hypot
conversion: to_int, to_bits, from_bits, to_string, parse_float

Bool​

.count() // count of `true` in a slice (extension idiom)
.to_int() // 0 or 1
.to_text() // "true" / "false"
parse_bool(text)

Char​

classify: is_alphabetic, is_numeric, is_whitespace, is_control,
is_uppercase, is_lowercase
case: to_uppercase, to_lowercase
encoding: encode_utf8, escape_debug
constants:Char.MIN Char.MAX Char.UNICODE_LIMIT

Byte​

is_ascii, is_ascii_digit, is_ascii_alphabetic, is_ascii_whitespace
to_int, to_char, to_text
Byte.from_int(n)

IDs and versioning​

uuid — RFC 4122 / 9562​

let v4 = Uuid.new_v4(); // 122 bits random
let v7 = Uuid.new_v7(); // 48-bit unix-ms + 74 random bits
let text: Text = v7.to_text();
let back = Uuid.parse(&text)?;

v7 is time-ordered — lexicographic sort matches chronological sort, ideal for DB primary keys (no B-tree fragmentation). The 48-bit timestamp prefix comes from core.time.system_time.SystemTime.now().timestamp_millis() (wall clock, clock_gettime(CLOCK_REALTIME) on Unix); the remaining 74 random bits come from the platform CSPRNG via core.sys.common.random_bytes.

snowflake — Twitter 64-bit IDs​

let mut gen = Snowflake.new(DEFAULT_EPOCH_MS, worker_id)?;
let id: UInt64 = gen.next_id()?;

// Decompose.
let parts = snowflake.parse(id, DEFAULT_EPOCH_MS);
// { timestamp_ms, worker_id, sequence }

Bit layout: [ 0 | 41-bit unix-ms | 10-bit worker | 12-bit seq ]. Monotonically increasing within a worker; the generator surfaces two distinct clock-regression errors so non-monotone wall clocks never silently produce non-monotone IDs:

  • ClockRegressed(delta_ms) — wall clock went backwards relative to this generator's last emitted ID.
  • ClockBeforeEpoch(delta_ms) — wall clock is earlier than the configured epoch (e.g. embedded systems with an unset RTC reading 1970 against the 2010 Twitter epoch, or tests using a future epoch). Pre-fix, the underlying subtraction underflowed UInt64 silently and the resulting bit-shifted value produced corrupt non-sortable IDs with no error surfaced.

The wall-clock source is core.time.system_time.SystemTime.now() .timestamp_millis() — clock_gettime(CLOCK_REALTIME) on Linux/macOS, GetSystemTimePreciseAsFileTime on Windows.

Saturates at 4 M IDs/sec per worker (12-bit sequence wraps each ms).

Pick UUID v4/v7 for cross-system interop, Snowflake for compact DB primary keys with explicit worker sharding.

nanoid — URL-safe short IDs​

let id = nanoid.generate(); // 21 chars ≈ 126 bits
let short = nanoid.generate_len(10);
let hex = nanoid.generate_with_alphabet(b"0123456789abcdef", 16);

Byte-exact compatible with the nanoid JS/Go/Rust/Python libraries. Rejection sampling over the smallest power-of-two mask covering the alphabet eliminates modulo-bias. Birthday collision ≈ 1 per 2.4 × 10¹⁸ IDs at default length.

semver — Semantic Versioning 2.0.0​

let v = semver.parse(&Text.from("1.0.0-beta.1+build.5"))?;
// v.major = 1, v.pre_release = ["beta", "1"], v.build_meta = ["build", "5"]

let a = semver.parse(&Text.from("1.0.0-alpha"))?;
let b = semver.parse(&Text.from("1.0.0"))?;
assert(semver.cmp(&a, &b) < 0); // release > prerelease (§11.3)

Strict §9 parser — leading zeros in numeric identifiers rejected. Full §11 total ordering: major/minor/patch → prerelease-vs-release → numeric < alphanumeric → fewer identifiers < more at common prefix equal. Build metadata ignored in precedence (§10).

glob — shell-style pattern matching​

glob.matches("src/**/*.rs", "src/foo/bar.rs") // true
glob.matches("*.rs", "src/lib.rs") // false — * doesn't cross /

let pat = glob.compile("target/**")?;
pat.matches_path("target/debug/verum") // true
FormMatches
*sequence of non-separator chars
**any number of path segments
?single non-separator char
[abc] / [a-z] / [!abc]class / range / negated
\cliteral c

** follows fnmatch(FNM_PATHNAME \| FNM_LEADING_DIR) — the Bazel / Cargo / Jest / .gitignore convention.


See also​

  • collections — List, Map, Set, Deque, BinaryHeap, BTreeMap, BTreeSet.
  • text — Text (UTF-8 string), Char, formatter, regex, tagged literals.
  • mem — CBGR allocator and the implementation under Heap / Shared.
  • sync — thread-safe equivalents of Cell, atomics, Mutex, RwLock.
  • Language → patterns — pattern syntax used with Maybe / Result.
  • Language → error handling — Result/Maybe/?/throws/try/recover.