Nocter v0.45.0 Release Notes
Nocter v0.45.0 makes every future T safe to drive without synchronously waiting for external progress. Synchronous waiting is now visible through the positive blocking callable effect, asynchronous I/O owns the canonical API names, and structured task composition gains deterministic races and timeouts.
This release intentionally renames several standard-library APIs. No compatibility aliases are retained.
Nonblocking Future Drive
Driving a future T may suspend until external progress becomes available, but it does not block the executor thread while waiting. This is a universal property of the future type rather than an optional noblock future T refinement.
Compiler-owned future constructors carry explicit drive-safety certification. Target closure checks that certification against the primitive result contract before MIR or machine lowering; later compiler stages do not infer safety from a function name or backend operation.
Positive Blocking Contracts
Callables that may wait synchronously for external progress use the blocking modifier:
blocking func read_configuration(): String! {
// This call may wait synchronously for filesystem progress.
}
The absence of blocking guarantees that a call will not synchronously wait for descriptors, files, clocks, processes, threads, name resolution, or contended synchronization. It does not promise bounded execution time or prohibit allocation. noalloc, blocking, async, and result provenance remain independent contracts.
Blocking effects close through direct calls, interface dispatch, structural callable values, closures, recursive groups, and destruction. An async body cannot call a blocking operation or hide one in implicit cleanup.
Canonical Asynchronous I/O
Where asynchronous and synchronous forms coexist, the asynchronous operation now owns the base name and the synchronous operation uses an explicit _blocking suffix:
let pending = net.connect_tcp("example.com", 443)
var stream = await pending?
let received = await stream.read(&+buffer)?
blocking func load_now(): void! {
var stream = net.connect_tcp_blocking("example.com", 443)?
let received = stream.read_blocking(&+buffer)?
return
}
This convention applies across TCP, TLS, HTTP, stream I/O, and sleep. Timeout qualifiers precede the execution qualifier, such as connect_tcp_with_timeout_blocking.
Host resolution for asynchronous TCP, TLS, and HTTP now occurs while the returned computation is driven. Public asynchronous setup exposes one deferred future T! outcome instead of an immediate fallible wrapper around another fallible future. Synchronous resolution remains available through an explicit blocking contract.
Structured Race and Timeout
std/task now provides deterministic two-computation race and timeout composition:
let winner = await task.race(first(), second())
match await task.with_timeout(
stream.read(&+buffer),
time.Duration.from_seconds(1),
) {
Timeout.completed(result) {
let count = result?
// Use count.
}
Timeout.elapsed {
// The read was cancelled before this result was returned.
}
}
task.race polls its left child first and selects the left result on a same-step tie. It cancels the loser before returning and transfers the winning output exactly once. task.with_timeout composes that race with asynchronous time.sleep; operation failures remain values inside Timeout.completed and are not conflated with elapsed time.
Nested joins, races, and timeouts forward their complete wait-interest sets to the existing executor. No second scheduler or process-global executor is introduced.
Ownership and Resource Disposal
Network connections and listeners use one nonblocking terminal-disposal path for explicit close, failure, cancellation, and implicit destruction. Native release barriers run on a cleanup worker instead of synchronously blocking future cancellation or drop. Winner output, loser captures, completed but unconsumed children, and nested task state are each destroyed exactly once.
Editor and Diagnostic Support
Formatting, hover, signature help, inlay hints, navigation, semantic diagnostics, and completion consume the same checked callable contracts as compilation. Completion offers noalloc, blocking, and async only in their valid declaration order. A rejected async-to-blocking call retains the authored blocking contract in editor presentation.
Migration and Non-goals
Migrate public asynchronous calls from names such as connect_tcp_async, read_async, and send_async to their base names. Migrate synchronous calls from the old base names to the corresponding _blocking forms. A synchronous function that reaches one of those operations must carry blocking, and such function and interface-callable contracts must admit that effect.
v0.45.0 does not add detached tasks, copyable task handles, a process-global executor, general threading, parallel CPU execution, realtime, nosuspend, bounded-work analysis, or another future type. Allocation and recoverable failure are not classified as blocking.
The supported distribution remains a self-contained arm64-darwin compiler and standard library.
Release Qualification
Release-content commit 03f80fa57ff9843a760742e0f7ac128473719ccb passed the complete compiler gate in a disposable target and two independent optimized package builds. The resulting archives and installed homes were identical. Fresh extraction passed the installed CLI, every public example, selected exact process contracts, native execution, framed LSP analysis, installed-home immutability, and compiler and standard-library tamper rejection.
The nocter-v0.45.0-arm64-darwin.tar.gz release is the exact retained qualified archive. It is 8,940,420 bytes with SHA-256 5be8dfee60b5d4d8a2f98071bc1f1a2056983a2406112a552791cadb766a3538; publication reused this retained archive without rebuilding it.