Nocter v0.48.0 Release Notes
Nocter v0.48.0 makes buffered byte I/O generic across files, standard streams, network streams, and user-defined byte sources and destinations. Both synchronous and asynchronous adapters now depend only on the corresponding public I/O interfaces instead of recognizing one concrete transport.
This release changes the previous file-specific blocking BufReader and BufWriter API. No compatibility aliases are retained.
Generic Buffered I/O
Synchronous code uses explicit blocking adapter names:
use std/io.File
use std/io/buffer.{BlockingBufReader, BlockingBufWriter}
var input = BlockingBufReader.new(File.open("input.txt")?)
var output = BlockingBufWriter.new(File.create("output.txt")?)
BlockingBufReader<R> accepts any R implementing BlockingReader. BlockingBufWriter<W> accepts any W implementing BlockingWriter. The buffer owns the underlying value and returns it through consuming finish; a writer flushes explicitly before returning its destination.
Executor-safe code uses the unqualified names:
use std/io/buffer.{BufReader, BufWriter}
var input = BufReader.with_capacity(move stream, 4096)
let line = await input.read_line()?
let source = input.finish()
BufReader<R> and BufWriter<W> are ordinary generic types over Reader and Writer. They do not add a transport-specific timeout or close capability. Callers recover the underlying value with finish and apply concrete operations there.
Line and Buffer Semantics
Reader line operations remove LF and one immediately preceding CR, preserve a final unterminated line, distinguish an empty line from EOF, and validate the complete result as UTF-8. A reusable destination keeps its allocation when capacity is sufficient. Requested zero capacity is normalized to one byte, so refill always has a positive progress boundary.
Both reader families validate implementation-reported byte counts before committing them. Invalid counts fail with std.io.invalid_read_count. Once a read or line operation fails after the source may have advanced, the adapter becomes terminal instead of retrying ambiguous input.
Writer buffers retain output privately until a full-buffer or explicit flush. Flush always reaches the underlying writer, including through an empty outer buffer or nested adapters. A failed write or flush makes the adapter terminal because the destination may already have accepted an unknown prefix. Dropping an unfinished writer discards retained bytes; destruction never hides a fallible flush.
Cancellation-safe Async State
The async reader owns one initialized fixed buffer and commits a separate logical valid length only after a successful checked read. Cancelling a refill cannot expose scratch initialization as input. If a line operation is cancelled after accepting a prefix, the reader retains that prefix; a later line operation resumes it and a later byte read returns it before newer bytes.
The async writer enters its terminal state before awaiting an underlying write or flush and returns to the open state only after complete success. Cancellation therefore cannot cause a later retry of an output prefix whose externally visible length is unknown. Underlying values are destroyed exactly once unless a successful consuming finish returns them.
Applications and Editor Support
The public async-loopback application performs a bounded newline-delimited request and response through BufReader<TcpStream> and BufWriter<TcpStream>. Blocking standard-input and recursive text-search examples use the explicit blocking adapters.
Hover, completion, signature help, declaration and implementation navigation, semantic tokens, and inlay hints use ordinary checked generic and interface semantics. The compiler and language server contain no registry, fallback, or presentation special case for buffer type names.
Migration and Non-goals
Code using the previous file-specific blocking BufReader or BufWriter must select BlockingBufReader<File> or BlockingBufWriter<File>. Async code uses the unqualified adapters and awaits their I/O methods. Generic code may substitute any type satisfying the matching interface.
v0.48.0 does not add asynchronous regular-file operations, worker threads, independently owned subprocess pipes, seeking, vectored I/O, memory mapping, or a generic asynchronous item-stream abstraction. The supported distribution remains a self-contained arm64-darwin compiler and standard library.
Release Qualification
Release-content commit 50ceaf9e3e215cb899ef1a572421bae6a881baa6 passed the complete compiler and documentation gates. Two independent optimized builds produced byte-identical archives and recursively identical installed homes. The retained archive is 8,964,395 bytes with SHA-256 898f4e7ed27b0c0eb6ae2344af3c7b60bbf728790ded1a5d4530e4347bca7057 and contains exactly 278 standard-library files. A fresh extraction passed version and installation diagnosis, locked/offline package workflows, native build and execution, every public example, framed LSP analysis, immutability checks, and compiler and standard-library tamper rejection. Publication reuses this retained archive without rebuilding it.