Module io/index
Async I/O traits — the D5 redesign (plans/archive/STD_API_AUDIT.md §D5).
Reader and Writer are the async byte-stream interfaces every I/O
source/sink implements: File (std/fs), TcpStream (std/net), and the
Stdin/Stdout/Stderr handles in std/io/stdio. All methods run on
the single-threaded async event loop and report failure by throwing
IoExn (D1 style 1 — everything on the io path throws).
The raw read/write take a caller buffer as *(u8) + length — the
same shape TcpStream.read and the sys layer already speak. Raw
pointers are not available in safe code, so IMPLEMENTING or calling the
raw methods directly needs pragma(Pragma.AllowUnsafe);; safe code uses
the pointer-free defaults (read_to_end, read_to_string) and the
per-type conveniences on each concrete type.
The generic BufReader(R) / BufWriter(W) wrappers landed in
./bufio.yo, and the std/sys/bufio → std/io move COMPLETED 2026-08-28
(fd-based pair deleted; the compiler's three stdin consumers read through
BufReader(Stdin)).
The Dyn(Reader) spelling works too (C17 fixed 2026-08-29; pinned in
tests/io/async_traits.test.yo): a Dyn(Reader) dispatches read and
the defaults through its vtable and can be the R of BufReader(R).
Concrete types carry NO duplicate spellings of the trait operations —
File/TcpStream read-everything is read_to_end/read_to_string, the
trait defaults (D2: one Rust-shaped name per operation); their
write_string/write_bytes conveniences stay because the trait has no
counterpart.
Seek(From) joins them as of 2026-09-09, and is the one trait here that
is SYNCHRONOUS — moving a position is arithmetic on a handle's own state,
not I/O. Implemented by File; see the trait's own doc for why it takes
the reference-point type as a parameter and has no rewind.
STILL TO COME: a buffered lines(). The async iterator protocol it was
waiting on landed 2026-09-11 (std/async/stream.yo), so the remaining
blocker is this trait's own error style: a Stream reports failure in its
ITEM and read/read_line THROW, which cannot be caught inside an async
body. See plans/backlog/ASYNC_LINES_NEEDS_A_NONTHROWING_READ.md for the
try_read proposal.
Stability
unstable — the traits' core signature is the open question. read and
write take a caller buffer as *(u8) + length because Yo has no slice
type; if one arrives, that parameter shape is what it would replace, and
that is a breaking change to every implementor (File, TcpStream,
UnixStream, TlsStream, the stdio handles, BufReader/BufWriter,
Child's pipe handles) at once. It is also why safe code cannot
implement these traits at all today, only consume them through the
pointer-free defaults. Two smaller items ride along: read_exact is a
BufReader method rather than the Reader default Rust has, and
lines() waits on the non-throwing read named above. Seek(From) is
newer still — 2026-09-09 — and inside its one-release window. copy, and
the read_to_end/read_to_string/write_all defaults, are expected to
keep their names and meanings.
Traits / Modules
Something whose read/write position can be moved — Rust's io::Seek.
SYNCHRONOUS, unlike Reader/Writer: moving a position is arithmetic on a
handle's own state, not I/O. File.seek resolves the target itself and
never touches the descriptor (its reads and writes are positional, so the
descriptor sits at 0 forever), so there is nothing to await. Keeping it
synchronous also means a Seek bound composes with a plain function.
SeekFrom lives in std/fs/types because that is where the File API it
was written for lives; the trait takes it as a parameter rather than
re-declaring it, so there is one spelling of "from where".
There is deliberately no rewind (Rust has one): every absolute move needs
a From VALUE naming the beginning, and a trait generic over From cannot
name one. A default rewind(self, from, exn) would make the caller pass the
very thing rewind exists to hide, so the convenience lives on the concrete
type instead — see File.rewind.
Type Parameters
| Name | Type | Notes |
|---|---|---|
From | Type | comptime |
An async source of bytes.
Methods
read : fn(self : Self, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)read_to_end : fn(self : Self, io : Io) -> Impl : (Future[Future](ArrayList(u8)) IoExn : IoExn)read_to_string : fn(self : Self, io : Io) -> Impl : (Future[Future](String) IoExn : IoExn)Read until end-of-stream and decode as UTF-8. Unlike the UNCHECKED
per-type conveniences that predate D5, this validates: malformed bytes
throw IoError.InvalidData (use read_to_end + String.from_utf8
to see the exact offset).
Parameters
| Name | Type | Notes |
|---|---|---|
self | Self | |
io | Io |
Implementors
An async sink of bytes.
Methods
write : fn(self : Self, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)flush : fn(self : Self, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)write_all : fn(self : Self, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)Implementors
Functions
Copy everything from r to w until r reaches end-of-stream,
resolving to the total number of bytes copied (u64 so totals larger
than a 32-bit usize cannot overflow). Does NOT flush w.
Type Parameters
| Name | Type | Notes |
|---|---|---|
R | Type | comptime |
W | Type | comptime |
Parameters
| Name | Type | Notes |
|---|---|---|
r | R | |
w | W | |
io | Io |
Returns: Impl(Future(u64, IoExn))