IO
Sync byte interfaces, descriptor-backed standard handles, and MemoryStream.
std.io defines sync byte-oriented interfaces, descriptor-backed standard
handles, descriptor wrappers, and in-memory streams. Async byte streams live in
std.aio.
import std.io;
fn main() {
io.println("hello");
}
io.stdin, io.stdout, and io.stderr borrow the process descriptors. They
do not close those descriptors when dropped.
The standard handles expose fallible read and write helpers:
try io.stdout.write("hello\n");
try io.stderr.write("error\n");
let line = try io.stdin.read_line();
let text = try io.stdin.read_text(32);
let n = try io.stdin.read(buffer);
write(...) must be handled with try or catch. read(buffer) returns a
byte count; 0 means EOF. read_text(byte_count) reads up to the requested
byte count and returns owned String. read_text and read_line validate
bytes as Zynx UTF-8 text without embedded NUL bytes, returning
utf8.UnicodeError.Invalid for malformed text. read_line() reads until
newline, excludes \n, strips a preceding \r, and returns
IOError.UnexpectedEofError only when EOF happens before any byte is read.
For simple human-facing CLI output, use the text helpers:
import std.io;
fn main() {
let package = "demo";
let missing = "zynx.lock";
io.println("package: {package}");
io.eprintln("missing file: {missing}");
}
io.print, io.println, io.eprint, and io.eprintln write borrowed text to
stdout or stderr and intentionally ignore low-level write status. Use
Writer.write or io.write when a program must handle I/O errors. Use string
interpolation to prepare formatted text before passing it to these helpers.
interface Reader {
fn read(self, buffer: [u8]) throws(IOError | AllocError) -> usize;
}
interface Writer {
fn write(self, data: bytes.Bytes) throws(IOError | AllocError) -> void;
}
interface Stream: Reader, Writer;
Reader.read(buffer) returns the number of bytes read. Writer.write(data)
writes all supplied bytes or returns an error. Writer APIs use canonical
bytes.Bytes inputs; callers can pass string literals, String, existing byte
views, or [u8] slices directly.
The shared helpers operate on sync interfaces:
| Item | Purpose |
|---|---|
io.print(text), io.println(text) | Write text to stdout, with println appending \n; low-level write status is ignored. |
io.eprint(text), io.eprintln(text) | Write text to stderr, with eprintln appending \n; low-level write status is ignored. |
io.write(writer, data) | Write byte-view data to any Writer. |
io.read(reader, buffer) | Fill the requested buffer exactly or return IOError.UnexpectedEofError. |
import std.io;
import std.mem;
fn main() {
let stream = io.MemoryStream();
try io.write(&stream, "abc");
stream.reset();
let exact = [0 as u8, 0 as u8, 0 as u8];
try io.read(&stream, mem.slice_of<u8>(exact.ptr, exact.length));
assert exact[2] == 99 as u8;
}
DescriptorReader, DescriptorWriter, and DescriptorStream wrap a numeric
descriptor without taking ownership of it.
import std.io;
import std.os;
fn main() {
let fds = [0, 0];
unsafe {
assert os.pipe(fds.ptr) == 0;
}
let reader = io.DescriptorReader(fds[0] as usize);
let writer = io.DescriptorWriter(fds[1] as usize);
try writer.write("ok\n");
let line = try reader.read_line();
assert line.str() == "ok";
unsafe {
_ = os.close(fds[0] as usize);
_ = os.close(fds[1] as usize);
}
}
| Method | Purpose |
|---|---|
DescriptorReader(descriptor), DescriptorWriter(descriptor), DescriptorStream(descriptor) | Create borrowed sync wrappers. |
descriptor() | Return the borrowed descriptor. |
read(buffer) | Read up to buffer.length bytes and return the byte count. |
read_text(byte_count) | Read up to a byte count and return owned String. |
read_line() | Read until newline, excluding \n and stripping a preceding \r. |
write(data) | Write all bytes from a canonical bytes.Bytes input. |
MemoryStream is an owned in-memory sync Stream. Writes append to a growable
buffer, reads consume from a cursor, and reset() moves the cursor back to the
beginning.
import std.io;
import std.mem;
import std.bytes;
fn main() {
let stream = try io.MemoryStream(bytes.Bytes("hi"));
let buffer = [0 as u8, 0 as u8];
assert (try stream.read(mem.slice_of<u8>(buffer.ptr, buffer.length))) == 2;
assert buffer[0] == 104 as u8;
assert buffer[1] == 105 as u8;
}
| Method | Purpose |
|---|---|
MemoryStream() | Create an empty stream with the default allocator. |
MemoryStream(allocator) | Create an empty stream with custom storage. |
MemoryStream(data) | Create a stream containing a copy of a byte view. |
read(buffer) | Copy bytes from the current cursor and return the byte count. |
write(data) | Append bytes to the stream. |
len(), capacity() | Inspect buffered size and allocated capacity. |
position(), remaining() | Inspect the read cursor. |
is_empty() | Return whether the stream has no buffered bytes. |
stream.bytes() | Borrow all buffered bytes as a read-only byte view. |
reset(), clear() | Move the cursor or remove buffered bytes. |
reserve(capacity), shrink() | Adjust backing storage. |
IOError includes common file and stream failure cases such as
FileNotFoundError, PermissionError, InterruptedError, TimeoutError, and
UnexpectedEofError. std.aio re-exports the same IOError type for async
byte streams.