6.2 KiB
std.process - Process API Reference (Zig 0.16.0)
Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html
Zig 0.16 moves process I/O, args, environment, current directory, and child process management behind explicit std.Io and Juicy Main.
Juicy Main
Preferred application entry:
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;
const arena = init.arena.allocator();
const args = try init.minimal.args.toSlice(arena);
const env = init.environ_map;
const preopens = init.preopens;
_ = .{ gpa, io, args, env, preopens };
}
std.process.Init provides:
minimal.argsminimal.environarenagpaioenviron_mappreopens
Use std.process.Init.Minimal only when a program deliberately wants less setup.
Args
Prefer args from std.process.Init:
const args = try init.minimal.args.toSlice(init.arena.allocator());
Avoid older global argument APIs in new 0.16 code unless you are inside compatibility code.
Environment
Prefer init.environ_map at application boundaries.
Important error rename:
error.EnvironmentVariableNotFound->error.EnvironmentVariableMissing
When spawning, pass an environment map through process options:
const result = try std.process.run(gpa, io, .{
.argv = &.{ "tool" },
.environ_map = init.environ_map,
});
defer gpa.free(result.stdout);
defer gpa.free(result.stderr);
Current Directory
var buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
const n = try std.process.currentPath(io, &buffer);
const cwd = buffer[0..n];
const cwd = try std.process.currentPathAlloc(io, gpa);
defer gpa.free(cwd);
On Windows these paths use WTF-8. On other platforms they are opaque path bytes with no guaranteed text encoding; do not assume UTF-8 when displaying or parsing them.
Do not add new std.process.getCwd* callsites.
Run and Capture Output
Use std.process.run(gpa, io, options).
const result = try std.process.run(gpa, io, .{
.argv = &.{ "git", "status", "--short" },
.stdout_limit = .limited(64 * 1024),
.stderr_limit = .limited(64 * 1024),
.cwd = .inherit,
});
defer gpa.free(result.stdout);
defer gpa.free(result.stderr);
switch (result.term) {
.exited => |code| if (code != 0) return error.CommandFailed,
else => return error.CommandFailed,
}
Important options:
argvstdout_limit/stderr_limitasstd.Io.Limitreserve_amountcwdenviron_mapexpand_arg0progress_nodecreate_no_windowdisable_aslrtimeout
Supplying .environ_map replaces the child's environment, but its PATH does
not resolve argv[0]; executable lookup still uses the parent environment.
Use std.process.spawnPath for deterministic directory-relative resolution.
Spawn Child Process
Use std.process.spawn(io, options). std.process.Child.init is not the 0.16 pattern.
var child = try std.process.spawn(io, .{
.argv = &.{ "tool", "--flag" },
.stdin = .ignore,
.stdout = .pipe,
.stderr = .pipe,
.cwd = .inherit,
});
defer child.kill(io);
const term = try child.wait(io);
_ = term;
child.wait(io) blocks until termination and cleans resources. child.kill(io) is uncancelable and idempotent after wait/kill.
Pipes
Pipe fields are std.Io.File values when requested.
var child = try std.process.spawn(io, .{
.argv = &.{ "cat" },
.stdin = .pipe,
.stdout = .pipe,
.stderr = .pipe,
});
defer child.kill(io);
var stdin_buf: [4096]u8 = undefined;
var stdin_writer = child.stdin.?.writer(io, &stdin_buf);
try stdin_writer.interface.writeAll("hello\n");
try stdin_writer.interface.flush();
child.stdin.?.close(io);
child.stdin = null;
var stdout_buf: [4096]u8 = undefined;
var stdout_reader = child.stdout.?.reader(io, &stdout_buf);
const stdout = try stdout_reader.interface.allocRemaining(gpa, .limited(64 * 1024));
defer gpa.free(stdout);
const term = try child.wait(io);
_ = term;
For simultaneous stdout/stderr capture, prefer std.process.run or std.Io.File.MultiReader to avoid pipe deadlocks.
Working Directory
Child cwd uses std.process.Child.Cwd:
.cwd = .inherit
.cwd = .{ .path = "projects" }
.cwd = .{ .dir = some_io_dir }
Use std.process.spawnPath(io, dir, options) when argv[0] should be resolved relative to a directory as a file path.
Standard I/O Options
SpawnOptions.StdIo values:
.inherit.file.ignore.pipe.close
Example:
var child = try std.process.spawn(io, .{
.argv = &.{ "tool" },
.stdin = .ignore,
.stdout = .pipe,
.stderr = .pipe,
});
Preopens
WASI preopens moved to std.process.Preopens and are exposed by std.process.Init.
const preopens = init.preopens;
_ = preopens;
Memory Locking
Memory locking/protection APIs moved under std.process:
std.process.lockMemorystd.process.unlockMemorystd.process.lockMemoryAllstd.process.unlockMemoryAllstd.process.MemoryProtectionstd.process.protectMemory
Use them only for explicit platform/security needs.
Migration Map
| Old pattern | Zig 0.16 pattern |
|---|---|
pub fn main() !void plus global args/env |
pub fn main(init: std.process.Init) !void |
std.process.Child.run(.{ ... }) |
std.process.run(gpa, io, .{ ... }) |
std.process.Child.init(argv, allocator) |
std.process.spawn(io, .{ .argv = argv, ... }) |
child.spawn() |
spawn returns the child |
child.wait() |
child.wait(io) |
child.kill() |
child.kill(io) |
std.process.getCwd(...) |
std.process.currentPath(io, ...) |
std.process.getCwdAlloc(...) |
std.process.currentPathAlloc(io, allocator) |
error.EnvironmentVariableNotFound |
error.EnvironmentVariableMissing |
Review Checklist
- Does the function already have
std.process.Initor anioparameter? - Are args/env taken from
initinstead of globals? - Are child processes bounded with output limits or timeouts where appropriate?
- Are pipe reads/writes using
std.Io.Filereaders/writers withio? - Is
child.kill(io)used indeferwhen early exits could leave a process alive? - Is stdout/stderr capture safe from deadlock?