⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

Hello World

Here is a complete Zig program. Press Run; it executes in your browser as WebAssembly.

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
}

Why does main take a parameter?

If you have seen Zig before, you probably expect pub fn main() !void. On current Zig master, main receives a std.process.Init:

pub fn main(init: std.process.Init) !void

That init carries an Io instance, init.io. This is the same idea as Allocator, applied to I/O: just as Zig never allocates behind your back, it now never blocks behind your back either. Any function that touches the filesystem, network, or clock takes an Io explicitly, so the caller decides whether that work is threaded, evented, or something else entirely.

The practical consequence is that you cannot reach for a global stdout. You must be handed the capability to write.

Buffering, and the flush you must not forget

writeStreamingAll is fine for a single fixed string, but real programs format output. For that you want a buffered writer:

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    var buf: [1024]u8 = undefined;
    var file_writer = std.Io.File.stdout().writerStreaming(init.io, &buf);
    const out = &file_writer.interface;

    try out.print("{s} v{d}.{d}\n", .{ "zig", 0, 16 });
    for (0..3) |i| try out.print("  line {d}\n", .{i});

    // Nothing is written until the buffer is drained.
    try out.flush();
}

Since Zig 0.15 (the release nicknamed “writergate”), writers are buffered and the buffer is part of the interface, not the implementation. You supply the memory, which means no hidden allocation.

The tradeoff is that nothing is written until you flush. Delete the try out.flush() line above, press Run, and you will get no output at all. Try it: click Edit, remove the line, and Run again.