Module io/bufio
Buffered wrappers over ANY async Reader/Writer — the D5 generic
BufReader(R) / BufWriter(W) (plans/archive/STD_API_AUDIT.md §D5), the
replacement for the fd-only std/sys/bufio pair it has now replaced.
BufReader(R) reduces the number of inner reads by filling an internal
buffer in capacity-sized chunks; BufWriter(W) batches small writes and
delivers them with write_all when the buffer fills or flush is called.
Both implement the trait they wrap, so a BufReader(R) is itself a
Reader (and gets read_to_end/read_to_string for free) and a
BufWriter(W) is a Writer.
BufWriter(W) does NOT flush on drop. A Dispose cannot await the
inner writer's async write, so — like tokio's BufWriter, and unlike
the deleted fd-based std/sys/bufio writer, whose dispose could issue a
sync positional syscall — buffered bytes still in the writer when it is
dropped are LOST. Call flush(io) before letting it go.
lines() is still absent, but no longer for the reason this doc used to
give. The async iterator protocol EXISTS now (std/async/stream.yo); what
blocks lines() is that a Stream reports failure IN its item while
read_line THROWS, and a ctl handler can neither be installed across a
suspension nor stored in a ref struct. So the line primitive is still
read_line (.None on clean EOF), and the fix is a Reader that can
return its failure — plans/backlog/ASYNC_LINES_NEEDS_A_NONTHROWING_READ.md.
The fd-based std/sys/bufio pair is GONE (2026-08-28): the compiler's own
three consumers — lsp/server, lsp/transport, check_watch — read stdin
through BufReader(Stdin) now, and the module and its test are deleted. That
migration waited on a seed carrying #299's shadow-registration fix, because
yo build compiles src/ with the SEED and the older one mis-emitted this
module's match bindings; v0.2.18 carries it.
Stability
unstable — one thing here is expected to move and one thing is waiting on
the language. read_exact is a BufReader METHOD returning
Option(ArrayList(u8)), where Rust's is a Read trait method: the
Rust-shaped end state is a Reader default (so a File or TcpStream
gets it unbuffered too), which would leave two spellings of one operation
unless this one is removed — a breaking change, so it waits for a window.
And lines() cannot exist until Yo has an async iterator protocol, which
is why read_line is the primitive; adding lines() later is additive,
but it may well change what shape read_line wants to be. The
no-flush-on-drop rule for BufWriter is NOT an open question — it is a
deliberate match for tokio's BufWriter (a Dispose cannot await), and
it is documented above because callers must act on it.
Types
Buffered reader over any Reader.
Type Parameters
| Name | Type | Notes |
|---|---|---|
R | Type | comptime |
Trait Implementations
impl(generic(R : Type), where(R <: Reader), BufReader(R), ...)
new : (fn(inner : R) -> Self)Wrap inner with the default 4096-byte buffer.
Returns: Self
with_capacity : (fn(inner : R, capacity : usize) -> Self)Wrap inner with a capacity-byte buffer.
Returns: Self
buffered : (fn(self : Self) -> usize)Number of bytes currently buffered.
Returns: usize
_fill : (fn(self : Self, io : Io) -> Impl(Future(usize, IoExn)))Refill the internal buffer from the inner reader. Resolves to the
number of bytes now buffered; 0 means the inner reader hit EOF.
Returns: Impl(Future(usize, IoExn))
read_line : (fn(self : Self, io : Io) -> Impl(Future(Option(String), IoExn)))Read one line (up to and including \n; the \n is consumed and NOT
included). Resolves to .None on clean EOF with no pending bytes.
The bytes are handed back UNVALIDATED via the unchecked constructor —
framing protocols read ASCII headers; validate with String.from_utf8
where the content is untrusted.
read_exact : (fn(self : Self, n : usize, io : Io) -> Impl(Future(Option(ArrayList(u8)), IoExn)))impl(generic(R : Type), where(R <: Reader), BufReader(R), Reader(...))
Buffered writer over any Writer.
Type Parameters
| Name | Type | Notes |
|---|---|---|
W | Type | comptime |
Trait Implementations
impl(generic(W : Type), where(W <: Writer), BufWriter(W), ...)
new : (fn(inner : W) -> Self)Wrap inner with the default 4096-byte buffer.
Returns: Self
with_capacity : (fn(inner : W, capacity : usize) -> Self)Wrap inner with a capacity-byte buffer.
Returns: Self
buffered : (fn(self : Self) -> usize)Number of bytes currently buffered.
Returns: usize
write_string : (fn(self : Self, s : String, io : Io) -> Impl(Future(unit, IoExn)))Buffer a String's bytes. The pointer-taking write is the trait's
primitive; this is the form callers actually want, and the fd-based
std/sys/bufio writer this module replaced had it — dropping it would
have made "write a string to a buffered writer" a pointer exercise.
Returns: Impl(Future(unit, IoExn))
write_bytes : (fn(self : Self, data : ArrayList(u8), io : Io) -> Impl(Future(unit, IoExn)))Buffer an ArrayList(u8). Companion to write_string, same rationale.
Returns: Impl(Future(unit, IoExn))
_flush_buf : (fn(self : Self, io : Io) -> Impl(Future(unit, IoExn)))Deliver the buffered bytes to the inner writer (without flushing IT).
Returns: Impl(Future(unit, IoExn))
impl(generic(W : Type), where(W <: Writer), BufWriter(W), Writer(...))
write : (fn(self : Self, buf : *u8, size : usize, io : Io) -> Impl(Future(usize, IoExn)))