Skip to main content

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:
buffer
[]u8
Internal buffer for buffered data
seek
usize
Current read position in the buffer
end
usize
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.
n
usize
required
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
usize
required
Amount to subtract from the limit
return
?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.
gpa
Allocator
required
General purpose allocator
StreamEnum
type
required
Enum type defining the streams to poll
files
PollFiles(StreamEnum)
required
File handles to monitor
return
Poller(StreamEnum)
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.
return
bool
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.
nanoseconds
u64
required
Timeout in nanoseconds
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
StreamEnum
required
Which stream to get the reader for
return
*Reader
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
StreamEnum
required
Which stream to extract data from
return
[]u8
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

Platform-Specific Behavior

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

  1. Always deinit pollers: Failing to call deinit() can leak resources and leave pending I/O operations.
  2. Handle EOF properly: Check for 0-length reads to detect end of stream.
  3. Use appropriate timeouts: Infinite polling can block your application; use pollTimeout() for responsive UIs.
  4. Manage buffer memory: Call toOwnedSlice() when you need to keep data beyond the lifetime of the poller.
  5. Platform considerations: Be aware of platform-specific I/O characteristics when writing cross-platform code.

Build docs developers (and LLMs) love