v0.16.0: Practical Failure Values
Status: published (2026-08-24). Phase 0 replaced the representation and practical API of the existing built-in error type. Phase 1 closes the trusted primitive boundary and qualifies that error implementation through the real bundled standard library. Qualification evidence belongs to the release preparation.
Goal
Keep error propagation uniform across packages while making a recovered failure safe to retain, enrich, classify, and report without preserving the storage lifetime of constructor arguments. Failure transport must remain statically laid out and must not introduce interface objects, vtables, reference counting, implicit error conversion, or user-defined failure payload types.
Phase 0: Owned Error Handles
The built-in error type becomes a move-only handle to one immutable runtime error node. A leaf node owns a snapshot of its open dotted code and UTF-8 message. A context node owns one additional message and the previously constructed error. Literal-only standard failures may use immutable compiler data, while runtime text is copied into independently owned error storage.
The public surface remains one common failure channel:
func load(path: &str): Config! {
let text = read(path) catch failure {
return failure.context("while reading the configuration")
}
return parse(&text)?
}
error.new snapshots its arguments and therefore publishes no input provenance. context consumes its receiver, preserves the root classification code, and adds an outer reporting frame. Readonly code, message, and exact-code classification operations borrow only the error value. No operation maps one failure type to another because no second failure type exists.
Runtime Contract
nocter-runtime-contract is the sole numeric authority for the error handle, node kinds, node fields, static/owned discrimination, and report scratch. Machine layout, generated destruction, and ARM64 lowering consume that closed schema. ARM64 must not retain a parallel error-layout table.
Owned nodes use allocation independent of an authored lexical region, so a propagated error never outlives its storage. Dynamic construction and context attachment abort if their infallible allocation fails. Allocation failure itself uses a prebuilt static error node and therefore does not allocate recursively.
The handle is move-only. Consequently every T! is move-only regardless of T; temporaries still use ordinary postfix ?, while a stored outcome requires the existing explicit move place? form. Error inspection borrows the handle and rethrowing moves it.
Reporting and Cleanup
The compiler-owned process wrapper reports the root code followed by outer-to-inner context and leaf messages, writes a trailing newline, destroys the complete owned node chain exactly once, and exits with status one. Reporting performs no allocation and remains best-effort when stderr cannot be written. Destruction walks the chain iteratively so authored context depth cannot consume the native call stack.
Completion Gate
Phase 0 is complete only when all of the following hold:
errorand everyT!are move-only in checking, ownership, deferred destruction, MIR, and native code;error.newaccepts runtime strings without publishing their provenance and no returned view can outlive the error handle;- exact-code classification, message access, context attachment, unchanged
?propagation, and catch-and-rethrow execute natively; - one static allocation-failure path proves that error construction cannot recurse on allocation failure;
- process reporting traverses the same runtime node schema used by accessors and destruction;
- Machine and ARM64 contain no independently authored error offset, tag, or size;
- hover, completion, references, rename, signature help, and semantic tokens expose the ordinary
source-backed
std/errorsurface rather than reconstructing built-in members; - focused semantic, ownership, layout, destruction, reporting, standard-library, and public-example tests pass together with the complete workspace, warnings-denied Clippy, formatting, generated documentation, and repository-integrity gates;
- a final authority review finds no borrowed constructor lifetime, leaked recovered node, hidden reference count, duplicated runtime schema, or error-specific semantic lookup in a backend.
Completion Evidence
Phase 0 completed on 2026-08-24.
nocter-runtime-contractowns the complete numeric handle, node-kind, node-field, static-leaf, and report-scratch schema. Machine layout and ARM64 lowering consume that schema; the former ARM64-local layout table was removed.- Checking makes
errorand everyT!unconditionally move-only. Concrete destruction gives the failure branch its own error cleanup, and MIR plus Machine lower that cleanup to one consumingReleaseErroroperation. - The compiler entry wrapper reports through a borrowed initialized error place and releases that same place afterward. It never copies the handle or reads a moved-from value.
- ARM64 native cases cover dynamic leaf construction, context order, code and message access, exact classification, static allocation failure, reporting, and iterative release. The static allocation-failure node executes without entering dynamic error construction.
- The standard surface is ordinary
std/errorsource. Completion, hover, definition, references, rename, signature help, and semantic tokens use the same general callable identities; no synthetic built-in field identity remains. - The final authority review found no constructor-input origin on owned errors, no backend access to semantic lookup, no duplicated numeric error layout, and no vtable or reference-count path.
cargo test --workspace --quietpassed the complete compiler workspace.cargo clippy --workspace --all-targets -- -D warnings,cargo fmt --all --check, documentation regeneration, andgit diff --checkpassed.
Phase 1: Minimal Trusted Exposure
The closed primitive registry distinguishes source-private, package-visible, and public exposure. This exposure class is part of the trusted primitive contract but remains separate from ordinary name resolution: declaration lowering owns normalized language visibility, while registry validation only verifies that the exact trusted declaration has its authorized exposure. Backend lowering receives the already selected primitive role and never interprets visibility.
Standard implementation primitives belong in the narrowest source that uses them. A primitive used only by one implementation source is private in that source. Cross-module runtime machinery remains pub(/) only when named callers in multiple standard modules require it. Descendant-only ordinary helpers use pub(./) rather than package visibility. Public standard contracts do not list private implementation primitives.
Real Standard-Library Qualification
Compiler-unit fixtures may retain minimal authored primitive surfaces for isolated semantic and backend tests, but those fixtures are not evidence that the bundled standard library is integrated correctly. One native acceptance path must compile the physical development/std package and exercise owned error construction, input snapshot independence, context attachment, borrowed code and message access, exact classification, propagation, catch-and-rethrow, process reporting, and the static allocation-failure path through public standard APIs.
Completion Gate
Phase 1 is complete only when all of the following hold:
- the primitive registry represents source-private, package-visible, and public exposure without a source-name fallback or backend visibility lookup;
- every bundled primitive and package-internal helper has the narrowest visibility required by its actual standard-library callers;
- implementation-only error, process, string-view, slice-view, and allocator-context primitives are absent from package namespaces;
- the allocation-failure primitive belongs to the memory implementation that uses it rather than the public error contract module;
- a real-standard-library native test covers dynamic and static errors, context, access, classification, propagation, rethrow, reporting, and cleanup-sensitive ownership;
- synthetic standard fixtures are identified as isolated compiler inputs and are not used as the sole evidence for bundled-standard behavior;
- focused registry, visibility, standard-library, command, and native tests pass together with the complete workspace, warnings-denied Clippy, formatting, generated documentation, and repository whitespace checks;
- a final authority review finds no primitive whose visibility is wider than its named callers, no duplicate standard API authority, and no later compiler stage re-evaluating source visibility.
Completion Evidence
Phase 1 completed on 2026-08-24.
- Discovery resolves standard semantic roles only from explicit contract declarations, while its separate primitive-role path resolves private declarations from included implementation sources. Primitive locators no longer carry a redundant syntax kind.
- Target validation owns the single registry exposure table and compares it with declaration
lowering's normalized
Visibility. MIR, Machine, ARM64, and Mach-O do not receive or reconstruct that visibility. - Error, allocator-context, process, string-subview, string-view, and slice-view primitives moved
from package contracts into their sole implementation sources. Unused syscall capabilities are
private in a dedicated system-primitive source. Remaining
pub(/)primitives have named callers in multiple standard modules; thestd/ioread/write helpers narrowed to descendant visibility. - Allocation-failure creation moved from
std/errorto the memory implementation that invokes it. The public error contract now contains only construction and instance APIs. tests/fixtures/standard/error-runtime.nctcompiles against the physicaldevelopment/stdpackage and passes native verification of dynamic message snapshots, context, borrowed accessors, classification, propagation, catch-and-rethrow, process reporting, static allocation failure, and ownership-sensitive cleanup.cargo test --workspace --quietpassed the complete compiler workspace. Warnings-denied Clippy, Rust formatting, documentation regeneration, and repository whitespace checks also passed.- The authority review found one owner for exposure policy, no package-name or spelling fallback, no backend visibility lookup, and no restricted primitive exposed wider than its current callers.
- The Phase 1 editor audit added an exact toolchain-standard LSP scope. Opening an installed standard contract or implementation source now retains the selected standard identity, catalogs the complete standard module set in discovery, and cannot register the same canonical root as a second path package.