This website is dedicated to helping programmers perform common tasks in Zig.
It is not a tutorial and should be used mostly as a reference.
Known Hosts
- https://renatoathaydes.github.io/zig-common-tasks/
- http://renatoathaydes.mypages.tech/zig-common-tasks/
Reading the samples
Notice that it’s implicit in many code samples:
const std = @import("std");at the top.- the
allocvariable is of typestd.mem.Allocator. See the allocators sample for how to get one.
All samples are tested on MacOS, Windows and Linux/Ubuntu using the Zig version selected in the selector in the top-right corner of this page.
Contributing
Read instructions on this project repository’s README on GitHub or OpenCode.
Index
Code samples
The Zig Standard Library is currently in flux. Effort is taken to keep all samples up-to-date, but changes are to be expected until it stabilizes after Zig 1.0.
Boolean logic (if expression, equality)
test "Basic boolean operators" {
// Zig uses `and` and `or`, unlike many languages that use `&&` and `||`
try std.testing.expect(true and true);
try std.testing.expect(false or true);
// but negation is still done using `!`
try std.testing.expect(!true or true);
}
test "If expressions" {
const x = true;
// Zig does not have the ternary operator (`x ? 1 : 0`), but it has `if` expressions!
try std.testing.expectEqual(42, if (x) 42 else 0);
}
test "Equality" {
// testing equality with `==` or `!=` works for numeric types, booleans and types
try std.testing.expect(2 == 1 + 1);
try std.testing.expect(false == (2 == 3 + 1));
try std.testing.expect(@TypeOf(true) == bool);
// but it doesn't work for other types, such as strings (or slices)
const array1 = [_]u8{ 1, 2, 3 };
var array2 = [_]u8{ 3, 2, 1 };
std.mem.sort(u8, &array2, {}, std.sort.asc(u8));
// the pointers are NOT the same (comparing arrays would not even compile)
try std.testing.expect(&array1 != &array2);
// even if the contents are the same
try std.testing.expect(std.mem.eql(u8, &array1, &array2));
}
// WARNING: @cImport will eventually go away.
// https://github.com/ziglang/zig/issues/20630
const c = @cImport({
// normally you would include headers or even c files, like this:
//@cInclude("test.c");
// but for this sample, C code is directly defined here
@cDefine("MY_C_CODE", "()\\const char* give_str() { return \\Hello C\\; }");
});
// use mem.span to convert strings to slices
const span = std.mem.span;
test "Handling C strings" {
// cstr has type '[*c]const u8' here, but can be coerced to a Zig pointer
const cstr = c.give_str();
// notice how Zig String literals are also 0-terminated
const hz: [*:0]const u8 = "Hello Zig";
const hc: [*:0]const u8 = "Hello C";
// calling "span" turns sentinel-terminated values into slices
const hzSlice: []const u8 = span(hz);
try expect(!std.mem.eql(u8, span(cstr), hzSlice));
try expect(std.mem.eql(u8, span(cstr), span(hc)));
}
Check for null (using nullable value)
test "Use value orelse a default" {
const byte: ?u8 = null;
const n: u8 = byte orelse @as(u8, 0);
try std.testing.expectEqual(n, @as(u8, 0));
}
test "Check for null using if statement" {
const byte: ?u8 = 10;
var n: u8 = 2;
if (byte) |b| {
// b is non-null here
n += b;
}
try std.testing.expectEqual(n, @as(u8, 12));
}
test "Check for null using if expression" {
const byte: ?u8 = null;
const n: u8 = if (byte) |b| b + 1 else 2;
try std.testing.expectEqual(n, @as(u8, 2));
} //
Notes:
- The get-command-line-args sample shows a while loop used with an iterator which returns null when no more elements are left.
test "Get the test allocator" {
const alloc = std.testing.allocator;
_ = alloc; // use allocator
}
test "Create a general-purpose allocator" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// call deinit to free it if necessary
defer _ = gpa.deinit();
_ = gpa.allocator(); // use allocator
}
test "Get system-native page allocator" {
const alloc: std.mem.Allocator = std.heap.page_allocator;
_ = alloc; // use allocator
}
test "Create a fixed buffer allocator" {
const alloc: std.mem.Allocator = init: {
// use an array as the "heap"
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
break :init fba.allocator();
};
_ = alloc; // use allocator
}
test "Get an Arena allocator" {
const alloc = std.heap.ArenaAllocator.init(std.heap.page_allocator);
// the arena allocator will free everything allocated through it
// when it is de-initialized.
defer alloc.deinit();
}
test "argsWithAllocator - get an iterator, use an allocator" {
var args = try std.process.argsWithAllocator(alloc);
defer args.deinit();
while (args.next()) |arg| {
_ = arg; // use arg
}
}
test "argsAlloc - get a slice, use an allocator" {
const args = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, args);
for (args) |arg| {
_ = arg; // use arg
}
}
test "args - get an iterator, no allocation but not fully portable" {
const builtin = @import("builtin");
var args =
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
// this sample does not work on Windows and WASI
return
else
// Linux, MacOS etc. can use the simpler args() method:
std.process.args();
while (args.next()) |arg| {
_ = arg; // use arg
}
} //
Notes:
- the first item in
argsis the program path itself. - not using the allocator version makes the code less cross-platform.
test "Get a single environment variable" {
const path = try std.process.getEnvVarOwned(alloc, "PATH");
defer alloc.free(path);
try std.testing.expect(path.len > 0);
}
test "Get all environment variables" {
var env = try std.process.getEnvMap(alloc);
defer env.deinit();
try std.testing.expect(env.count() > 0);
} //
const http = std.http;
const alloc = std.testing.allocator;
fn server_starter() !void {
const server_address = try std.net.Address.resolveIp("127.0.0.1", 8080);
var server = try server_address.listen(.{ .reuse_address = true });
// This would normally be an infinite loop!
// But for testing, we only handle a single request and stop.
const client_con = server.accept() catch |err| {
std.log.err("Unable to accept client connection: {s}", .{@errorName(err)});
return;
};
handle_connection(client_con) catch |err| {
std.log.err("Failed to handle request: {s}", .{@errorName(err)});
return;
};
}
/// Handles connection by always responding with a hardcoded response (ok for GET, method_not_allowed otherwise).
/// Normally you would inspect the request and spawn a new Thread (or use async, coming soon to Zig!).
fn handle_connection(client_con: std.net.Server.Connection) !void {
defer client_con.stream.close();
const read_buffer = try alloc.alloc(u8, 8 * 1024);
defer alloc.free(read_buffer);
const write_buffer = try alloc.alloc(u8, 8 * 1024);
defer alloc.free(write_buffer);
var con_reader = client_con.stream.reader(read_buffer);
var con_writer = client_con.stream.writer(write_buffer);
var server = http.Server.init(con_reader.interface(), &con_writer.interface);
var req = try server.receiveHead();
if (req.head.method == .GET) {
try req.respond("Hello\\", .{ .extra_headers = &.{.{ .name = "Content-Type", .value = "text/plain" }} });
} else {
try req.respond("", .{ .status = .method_not_allowed });
}
}
test "HTTP Server and HTTP Client" {
// The Server needs to run on a separate Thread.
const server_thread = try std.Thread.spawn(.{ .allocator = alloc }, server_starter, .{});
defer server_thread.join();
// Create a HTTP client that will send a single request to the Server.
var client = http.Client{ .allocator = alloc };
defer client.deinit();
const uri = try std.Uri.parse("http://localhost:8080/hello");
// create a GET request with some headers
var req = try client.request(http.Method.GET, uri, .{ .headers = .{
.user_agent = .{ .override = "zig-example-http-client" },
}, .extra_headers = &.{.{ .name = "Accept", .value = "text/plain" }} });
defer req.deinit();
// send it!
try req.sendBodiless();
// receive the request header
var response = try req.receiveHead(&.{});
// first, make sure the status code was 200 OK
try std.testing.expectEqual(.ok, response.head.status);
// we can iterate over the response headers
var resp_headers = response.head.iterateHeaders();
var content_type_found = false;
var content_length: usize = 0;
while (resp_headers.next()) |header| {
if (std.ascii.eqlIgnoreCase(header.name, "Content-Length")) {
content_length = try std.fmt.parseUnsigned(usize, header.value, 10);
} else if (std.ascii.eqlIgnoreCase(header.name, "Content-Type")) {
content_type_found = true;
try std.testing.expectEqualSlices(u8, header.value, "text/plain");
}
}
try std.testing.expectEqual(@as(usize, 6), content_length);
try std.testing.expect(content_type_found);
// read the full response body
const body = try alloc.alloc(u8, content_length);
defer alloc.free(body);
var body_reader = response.reader(body);
try body_reader.fill(content_length);
// assert the response body is as expected
try std.testing.expectEqual(6, body.len);
const expected_body = "Hello\\";
try std.testing.expectEqualSlices(u8, expected_body, body);
}
test "Parse JSON object" {
const example_json: []const u8 =
\\{"a_number": 10, "a_str": "hello"}
;
const JsonStruct = struct {
a_number: u32,
a_str: []const u8
};
const parsed = try std.json.parseFromSlice(JsonStruct, alloc, example_json, .{});
defer parsed.deinit();
const result = parsed.value;
try std.testing.expectEqual(@as(u32, 10), result.a_number);
try std.testing.expectEqualSlices(u8, "hello", result.a_str);
}
test "Write JSON object" {
const example_json: []const u8 =
\\{"a_number":10,"a_str":"hello"}
;
var buffer: [256]u8 = undefined;
var writer = std.Io.Writer.fixed(&buffer);
try std.json.Stringify.value(.{
.a_number = @as(u32, 10),
.a_str = "hello",
}, .{}, &writer);
try std.testing.expectEqualSlices(u8, example_json, buffer[0..writer.end]);
}
test "List contents of directory" {
var children = std.fs.cwd().openDir("source", .{ .iterate = true }) catch {
// couldn't open dir
return;
};
defer children.close();
// use the returned iterator to iterate over dir contents
_ = children.iterate();
} //
test "Simple for-each loop" {
const items = [_]i32{ 1, 2, 3, 4 };
var sum: i32 = 0;
for (items) |item| {
sum += item;
}
try std.testing.expectEqual(sum, 1 + 2 + 3 + 4);
}
test "Looping with indexes" {
var items: [4]i32 = undefined;
// the first item is the range being iterated over, the second gives the index
for (4..8, 0..) |value, index| {
items[index] = @as(i32, @intCast(value));
}
// notice that the range end is excluded
const expected = [_]i32{ 4, 5, 6, 7 };
try std.testing.expectEqualSlices(i32, &expected, &items);
}
test "For expression" {
const x = for (1..10) |i| {
if (i == 10) {
break "too big"; // if break is called within the for loop, this is the result
}
} else els: { // if break is not called, the else block gives the result
break :els "ok";
};
// if the range was 1..11, the x would be "too big"!
try std.testing.expectEqualSlices(u8, "ok", x);
}
test "Simple while loop" {
var done = false;
// as in most other languages, "while" can just take a boolean variable
while (!done) {
done = true;
}
try std.testing.expect(done);
}
test "While not null" {
// creating an iterable-like struct for this example
const Items = struct {
items: []i32,
index: usize = 0,
pub fn nextItem(self: *@This()) ?i32 {
if (self.index >= self.items.len) {
return null;
}
defer self.index += 1;
return self.items[self.index];
}
};
var items_array = [_]i32{ 1, 2, 3 };
var items: Items = .{ .items = &items_array };
var sum_before_null: i32 = 0;
var has_null = false;
// loop while the iterable does not return null
while (items.nextItem()) |item| {
sum_before_null += item;
} else { // when a null is found, the else branch runs and the loop exits
has_null = true;
}
try std.testing.expect(has_null);
try std.testing.expectEqual(1 + 2 + 3, sum_before_null);
}
test "While not error" {
// creating a struct for this example
const Positive = struct {
value: u32,
pub fn decrement(self: *@This()) void {
if (self.value > 0) self.value -= 1;
}
pub fn get(self: *@This()) anyerror!u32 {
if (self.value == 0) return error.ZeroValue;
return self.value;
}
};
var positive: Positive = .{ .value = 2 };
var sum: u32 = 0;
// while no error, keep looping... decrement the value after each iteration.
while (positive.get()) |value| : (positive.decrement()) {
sum += value;
} else |err| { // the optional else branch can capture the final error
std.debug.assert(err == error.ZeroValue);
}
try std.testing.expectEqual(3, sum);
}
test "Stack allocated data" {
const Example = struct {
fn createDataOnTheStack() anyerror![]u8 {
// unless using allocators, data is allocated on the stack, not heap
var data = [_]u8{ 1, 2 };
// data on the stack can only be used while in the scope of the function
try std.testing.expectEqual(1, data[0]); // OK
// very, very bad
return &data;
}
};
// using the returned value here would be unchecked Illegal Behavior,
// see: https://ziglang.org/documentation/master/#toc-Illegal-Behavior
_ = try Example.createDataOnTheStack();
}
test "Allocator-owned data" {
const Example = struct {
// in Zig, it is good practice to always take an allocator as an argument
// in any function that needs to allocate memory outside the stack.
fn createDataWithAllocator(allocator: std.mem.Allocator) anyerror![]u8 {
const local_data = [_]u8{ 1, 2 };
const data = try allocator.alloc(u8, 2);
@memcpy(data, &local_data);
return data; // OK, data is not allocated on the stack!
}
};
const alloc = std.testing.allocator;
const data = try Example.createDataWithAllocator(alloc);
// Allocator-allocated data must be freed using the same allocator!
// In tests, use `std.testing.allocator` and Zig will detect memory leaks and other errors.
// In Debug mode, you will get warnings or errors when using `std.heapGeneralPurposeAllocator`
// in cases like double-free, use-after-free and memory leaks.
defer alloc.free(data);
try std.testing.expectEqual(1, data[0]); // OK!
// Zig does bounds-checking in safe mode (it panics on error).
// To opt-out, use `-O ReleaseFast`
try std.testing.expectEqual(2, data[1]); // OK!
}
Print something to stdout/stderr
pub fn hello_world() !void {
// to print something for debugging purposes, there's an easy way!
std.debug.print("Hello world!\\", .{});
// but on real apps, you will want to use the "real" stdout/stderr
var buffer: [1024]u8 = undefined;
// Choose File.stdout() or File.stderr()
// Note: don't use stdout() in tests, it will block your test on "zig build test"!
var writer = std.fs.File.stderr().writer(&buffer);
const stderr = &writer.interface;
try stderr.print("Hello world!\\", .{});
// you can provide arguments, similar to C's printf,
// though in Zig the format is checked at compile-time!
try stderr.print("number: {d}, string: {s}\\", .{ 42, "fourty-two" });
try stderr.flush(); // Don't forget to flush!
} //
test "Execute a process (inherit stdout and stderr)" {
// the command to run
const argv = [_][]const u8{ "ls", "./" };
// init a ChildProcess... cleanup is done by calling wait().
var proc = std.process.Child.init(&argv, alloc);
// ignore the streams to avoid the zig build blocking...
// REMOVE THESE IF YOU ACTUALLY WANT TO INHERIT THE STREAMS.
proc.stdin_behavior = .Ignore;
proc.stdout_behavior = .Ignore;
proc.stderr_behavior = .Ignore;
try proc.spawn();
//std.debug.print("Spawned process PID={}\\", .{proc.id});
// the process only ends after this call returns.
const term = try proc.wait();
// term can be .Exited, .Signal, .Stopped, .Unknown
try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 });
}
test "Execute a process (consume stdout and stderr into allocated memory)" {
// the command to run
const argv = [_][]const u8{ "ls", "./" };
const proc = try std.process.Child.run(.{
.argv = &argv,
.allocator = alloc,
});
// on success, we own the output streams
defer alloc.free(proc.stdout);
defer alloc.free(proc.stderr);
const term = proc.term;
try std.testing.expectEqual(std.process.Child.Term{ .Exited = 0 }, term);
try std.testing.expect(proc.stdout.len != 0);
try std.testing.expectEqual(proc.stderr.len, 0);
}
test "Read bytes from a file" {
// current working directory
const cwd = std.fs.cwd();
const handle = cwd.openFile("hello.txt",
// this is the default, so could be just '.{}'
.{ .mode = .read_only }) catch {
// file not found
return;
};
defer handle.close();
// read into this buffer
var buffer: [64]u8 = undefined;
const bytes_read = handle.readAll(&buffer) catch unreachable;
// if bytes_read is smaller than buffer.len, then EOF was reached
try std.testing.expectEqual(@as(usize, 6), bytes_read);
const expected_bytes = [_]u8{ 'h', 'e', 'l', 'l', 'o', '\\' };
try std.testing.expectEqualSlices(u8, &expected_bytes, buffer[0..bytes_read]);
}
test "Read file one line at a time" {
const max_bytes_per_line = 4096;
var file = std.fs.cwd().openFile("my-file.txt", .{}) catch {
// couldn't open file
return;
};
defer file.close();
var read_buffer: [max_bytes_per_line]u8 = undefined;
var reader = file.readerStreaming(&read_buffer).interface;
while (try reader.takeDelimiter('\\')) |line| {
// use line
_ = line;
}
} //
Read user input from command line
pub fn main() !void {
const stdin = std.io.getStdIn().reader();
const maybe_input = try stdin.readUntilDelimiterOrEofAlloc(alloc, '\\', max_line_size);
if (maybe_input) |input| {
defer alloc.free(input);
// use input
}
} //
Socket - accept TCP connections
fn localhostListener(port: u16) !std.net.Server {
const localhost = try std.net.Address.resolveIp("0.0.0.0", port);
return localhost.listen(.{});
}
pub fn main() !void {
var listener = try localhostListener(8081);
defer listener.deinit();
const connection = try listener.accept();
defer connection.stream.close();
std.log.info("Accepted connection from {}", .{connection.address});
try printMessage(&connection.stream.reader());
} //
Notes:
- See also MasterQ32/zig-network.
- This sample does not work on Windows. Help to fix it welcome.
pub fn main() !void {
const remote = try std.net.Address.resolveIp("0.0.0.0", 8081);
var remote_stream = try std.net.tcpConnectToAddress(remote);
defer remote_stream.close();
try remote_stream.writer().writeAll("hello from Zig\\");
} //
Notes:
- See also MasterQ32/zig-network.
- This sample does not work on Windows. Help to fix it welcome.
String - check if it contains another
test "Check if a string contains another" {
const to_find = "string";
const contains = std.mem.containsAtLeast(u8, "big string", 1, to_find);
try std.testing.expect(contains);
} //
test "Check if two strings are equal ignoring case (ASCII only)" {
const are_equal = std.ascii.eqlIgnoreCase("hEllO", "Hello");
try std.testing.expect(are_equal);
}
test "Check if two strings are exactly equal" {
const are_equal = std.mem.eql(u8, "hello", "hello");
try std.testing.expect(are_equal);
} //
test "Convert string to number" {
// base-10 signed integer
const decimal = try std.fmt.parseInt(i32, "42", 10);
try std.testing.expectEqual(@as(i32, 42), decimal);
// floating-point number
const float = try std.fmt.parseFloat(f64, "3.1415");
try std.testing.expectEqual(@as(f64, 3.1415), float);
// base-2 unsigned integer
const byte = try std.fmt.parseUnsigned(u8, "1_0101", 2);
try std.testing.expectEqual(@as(u8, 21), byte);
} //
String - iterate over UTF-8 code points
test "Iterate over utf-8 string code points" {
// reference: https://emojipedia.org/woman-astronaut/
var utf8_str = (try std.unicode.Utf8View.init("👩🚀")).iterator();
try std.testing.expectEqual(@as(?u21, 0x1F469), utf8_str.nextCodepoint());
try std.testing.expectEqual(@as(?u21, 0x200D), utf8_str.nextCodepoint());
try std.testing.expectEqual(@as(?u21, 0x1F680), utf8_str.nextCodepoint());
try std.testing.expectEqual(@as(?u21, null), utf8_str.nextCodepoint());
} //
const Val = std.atomic.Value(u32);
const Order = std.builtin.AtomicOrder;
fn toRunInThread(v: *Val) void {
const value = v.load(Order.acquire);
v.store(value + 1, Order.release);
}
test "Pass an atomic value to a Thread and wait for it to be modified" {
const allocator = std.heap.page_allocator;
// an atomic value to be updated by another Thread
var value = Val.init(42);
const thread = try std.Thread.spawn(.{
// optional config
.allocator = allocator,
.stack_size = 1024,
}, toRunInThread, .{&value});
// must either join() or detach()
thread.join();
try expectEqual(@as(u32, 43), value.load(Order.unordered));
} //
test "Write bytes to a file (create if necessary, append or replace contents)" {
// current working directory
const cwd = std.fs.cwd();
const file_path = "hello.txt";
const handle = try cwd.createFile(file_path, .{
// set to true to fully replace the contents of the file
.truncate = false,
});
defer handle.close();
defer cwd.deleteFile(file_path) catch unreachable;
// go to the end of the file if you want to append to the file
// and `truncate` was set to `false` (otherwise this is not needed)
try handle.seekFromEnd(0);
// write bytes to the file
const bytes_written = try handle.write("hello Zig\\");
try std.testing.expectEqual(@as(usize, 10), bytes_written);
}
const c_code =
\\
\\static const void* _value;
\\void store_void(const void* v) { _value = v; }
\\const void* get_void() { return _value; }
;
// WARNING: @cImport will eventually go away.
// https://github.com/ziglang/zig/issues/20630
const c = @cImport({
// normally you would include headers or even c files, like this:
//@cInclude("test.c");
// but for this sample, C code is directly defined here
@cDefine("MY_C_CODE", c_code);
});
test "deal with void pointer from C" {
// send some value to be stored in C as a void* pointer
c.store_void(@as(?*const anyopaque, &@as(u32, 10)));
// retrieve the value which now is "untyped", or opaque
const untyped_value: ?*const anyopaque = c.get_void();
// to restore the type, we must cast it to the known type explicitly
const value: *const u32 = @ptrCast(@alignCast( untyped_value));
try std.testing.expectEqual(@as(u32, 10), value.*);
}