I/O API
The std.Io module provides input/output operations and streaming interfaces for Zig.
Core Types
Reader
pub const Reader = @import("Io/Reader.zig");
Provides a streaming reader interface for reading data.
Fields:
Internal buffer for buffered data
Current read position in the buffer
End position of valid data in the buffer
Writer
pub const Writer = @import("Io/Writer.zig");
Provides a streaming writer interface for writing data.
Limit
pub const Limit = enum(usize) {
nothing = 0,
unlimited = std.math.maxInt(usize),
_,
};
Represents a limit on the amount of data to process.
Limit Methods
limited
pub fn limited(n: usize) Limit
Creates a limit from a usize value.
The limit value (std.math.maxInt(usize) means unlimited)
limited64
pub fn limited64(n: u64) Limit
Creates a limit from a u64 value, capping at maxInt(usize).
min
pub fn min(a: Limit, b: Limit) Limit
Returns the minimum of two limits.
minInt
pub fn minInt(l: Limit, n: usize) usize
Returns the minimum of a limit and an integer.
slice
pub fn slice(l: Limit, s: []u8) []u8
Reduces a slice to respect the limit.
Example:
const limit = Limit.limited(10);
var data = [_]u8{0} ** 100;
const limited_data = limit.slice(&data); // Returns first 10 bytes
subtract
pub fn subtract(l: Limit, amount: usize) ?Limit
Reduces the limit by an amount, returning null if the limit would be exceeded.
Amount to subtract from the limit
New reduced limit, or null if amount exceeds the limit
Polling
poll
pub fn poll(
gpa: Allocator,
comptime StreamEnum: type,
files: PollFiles(StreamEnum),
) Poller(StreamEnum)
Creates a poller for monitoring multiple streams.
General purpose allocator
Enum type defining the streams to poll
files
PollFiles(StreamEnum)
required
File handles to monitor
Poller instance for managing I/O events
Example:
const StreamType = enum { stdin, file1, file2 };
const files = .{
.stdin = std.io.getStdIn(),
.file1 = try std.fs.cwd().openFile("data1.txt", .{}),
.file2 = try std.fs.cwd().openFile("data2.txt", .{}),
};
var poller = std.Io.poll(allocator, StreamType, files);
defer poller.deinit();
Poller Type
The Poller type manages polling multiple I/O streams.
poll (method)
pub fn poll(self: *Self) !bool
Polls all streams for available data.
true if data is available or streams are active, false if all streams are closed
pollTimeout
pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool
Polls streams with a timeout.
Example:
const has_data = try poller.pollTimeout(1000 * std.time.ns_per_ms); // 1 second
reader
pub fn reader(self: *Self, which: StreamEnum) *Reader
Gets a reader for a specific stream.
Which stream to get the reader for
Pointer to the reader for the specified stream
toOwnedSlice
pub fn toOwnedSlice(self: *Self, which: StreamEnum) error{OutOfMemory}![]u8
Extracts buffered data from a stream as an owned slice.
Which stream to extract data from
Owned slice containing the buffered data (caller must free)
deinit
pub fn deinit(self: *Self) void
Frees resources and cancels any pending I/O operations.
Example:
var poller = std.Io.poll(allocator, StreamType, files);
defer poller.deinit();
while (try poller.poll()) {
const stdin_reader = poller.reader(.stdin);
const data = stdin_reader.buffered();
// Process data...
}
TTY Support
pub const tty = @import("Io/tty.zig");
Provides terminal (TTY) functionality:
- Terminal detection
- Color support detection
- Terminal configuration
- ANSI escape sequence handling
Windows
On Windows, the I/O system uses:
OVERLAPPED structures for asynchronous operations
WaitForMultipleObjects for polling
- Small buffers for efficient async reads
POSIX
On POSIX systems, the I/O system uses:
poll() system call for monitoring file descriptors
- Direct read/write operations
- Standard error handling via errno
Error Handling
Common I/O Errors
// Reader errors
error.EndOfStream
error.ReadFailed
error.OutOfMemory
// Writer errors
error.WriteFailed
error.BrokenPipe
error.OutOfMemory
Usage Patterns
Buffered Reading
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const StreamType = enum { stdin };
const files = .{ .stdin = std.io.getStdIn() };
var poller = std.Io.poll(allocator, StreamType, files);
defer poller.deinit();
while (try poller.poll()) {
const r = poller.reader(.stdin);
const buffered = r.buffered();
if (buffered.len > 0) {
std.debug.print("Read: {s}\n", .{buffered});
r.discard(buffered.len);
}
}
}
Multiple Stream Polling
const StreamType = enum { file1, file2, file3 };
const files = .{
.file1 = try std.fs.cwd().openFile("data1.txt", .{}),
.file2 = try std.fs.cwd().openFile("data2.txt", .{}),
.file3 = try std.fs.cwd().openFile("data3.txt", .{}),
};
defer files.file1.close();
defer files.file2.close();
defer files.file3.close();
var poller = std.Io.poll(allocator, StreamType, files);
defer poller.deinit();
while (try poller.pollTimeout(100 * std.time.ns_per_ms)) {
inline for (@typeInfo(StreamType).@"enum".fields) |field| {
const stream = @field(StreamType, field.name);
const r = poller.reader(stream);
const data = r.buffered();
if (data.len > 0) {
std.debug.print("{s}: {s}\n", .{ field.name, data });
r.discard(data.len);
}
}
}
Limit-Based Reading
const limit = std.Io.Limit.limited(1024); // Read at most 1KB
var remaining = limit;
while (remaining.nonzero()) {
const chunk_size = remaining.minInt(256);
var buf: [256]u8 = undefined;
const n = try file.read(buf[0..chunk_size]);
if (n == 0) break; // EOF
// Process data...
remaining = remaining.subtract(n) orelse break;
}
Best Practices
-
Always deinit pollers: Failing to call
deinit() can leak resources and leave pending I/O operations.
-
Handle EOF properly: Check for 0-length reads to detect end of stream.
-
Use appropriate timeouts: Infinite polling can block your application; use
pollTimeout() for responsive UIs.
-
Manage buffer memory: Call
toOwnedSlice() when you need to keep data beyond the lifetime of the poller.
-
Platform considerations: Be aware of platform-specific I/O characteristics when writing cross-platform code.