Example of using a library: argonaut
We are going to use a library to add a very common feature when creating command-line tools: parsing arguments.
At https://zigistry.dev/ You can search for libraries of all kinds. From there, I have chosen one that seemed intuitive for this purpose.
It is called argonaut: https://codeberg.org/OhMyDitzzy/argonaut.
The description of the library as of December 2025 is the following:
Summary of the argonaut repository
“A flexible and powerful command-line argument parser for Zig, inspired by the Go argparse library. This library provides a clean and intuitive API for building complex CLI applications with support for subcommands, various argument types, validation, and more.”
Features
- Multiple Argument Types: Flags, counters, strings, integers, floats, files, and lists
- Subcommands: Build complex CLI tools with nested commands (like git)
- Positional Arguments: Support for positional arguments alongside named flags
- Validation: Custom validation functions for arguments
- Default Values: Set default values for optional arguments
- Selector Arguments: Restrict argument values to a predefined set
- Automatic Help: Built-in help generation with customizable formatting
- Type Safety: Fully type-safe argument handling
- Modular Design: Clean separation of concerns for easy maintenance
Installation instructions from repository
Add the dependency in your build.zig.zon by running the following command:
zig fetch --save=argonaut
https://codeberg.org/OhMyDitzzy/argonaut/archive/master.tar.gz
Then in your build.zig:
|
exe.root_module.addImport( "argonaut", b.dependency("argonaut", .{ .target = target, .optimize = optimize }).module("argonaut")); |
Once we have read the library description, we are going to create our program, which we will call argdemo:
mkdir argdemo;
cd argdemo;
zig init --minimal
Zig will create a minimal configuration. Immediately after that, we modify the build.zig.zon and build.zig files.
As we saw in the library README.md, we run:
zig fetch --save=argonaut https://codeberg.org/OhMyDitzzy/argonaut/archive/master.tar.gz
This command will download the library and modify the build.zig.zon file.
Configuration files and the program
build.zig.zon
|
.{ .name = .argdemo, .version = "0.0.1", .dependencies = .{ .argonaut = .{ .url = "https://codeberg.org/…", .hash = "...", }, }, .minimum_zig_version = "0.15.2", .paths = .{ "build.zig", "build.zig.zon", "src", }, .fingerprint = <here Zig generates a fingerprint>, } |
zig fetch will record the library as a dependency, but we still need to set the .paths for the files.
build.zig
|
const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = "argdemo", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }), }); exe.root_module.addImport("argonaut", b.dependency("argonaut", .{ .target = target, .optimize = optimize, }).module("argonaut")); b.installArtifact(exe); const run_step = b.step("run", "Run the app"); const run_cmd = b.addRunArtifact(exe); if (b.args) |args| { run_cmd.addArgs(args); } run_step.dependOn(&run_cmd.step); run_cmd.step.dependOn(b.getInstallStep()); } |
After modifying these files, we create the program's source code file, src/main.zig:
src/main.zig
|
const std = @import("std"); const print = std.debug.print; // We follow the instructions from the argonaut repository const argsparse = @import("argonaut"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Create a parser with a program name and description const parser = try argsparse.newParser( allocator, "Argdemo", "Just a demo application", ); defer parser.deinit(); var name_opts = argsparse.Options{ .required = true, .help = "Your name", }; const s_name = try parser.string( "n", "name", &name_opts, ); // create a function to validate the age const fn_validate_age = struct { // *const fn (args: []const []const u8) anyerror!void; fn _(args: []const []const u8) !void { if (args.len == 0 or args.len > 1) return; const n_age = std.fmt.parseInt( i64, args[0], 10, ) catch return error.InvalidNumber; if (n_age < 18) return error.AgeTooLow else if (n_age > 120) return error.AgeTooHigh else return; } }._; var age_opts = argsparse.Options{ .required = false, .help = "Your age as a number. Only accepted between 18 and 120", .default_int = 33, .validate = fn_validate_age, }; const n_age = try parser.int( "a", "age", &age_opts, ); const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); _ = parser.parse(args) catch { const usage_text = try parser.usage(null); defer allocator.free(usage_text); std.debug.print("{s}", .{usage_text}); std.process.exit(1); }; print("Hello: {s}, your age: {d} \n", .{ s_name.*, n_age.*, }); } |
Using the library is straightforward if we follow the instructions on the repository page itself. We import the library in the same way we usually do with std.
We copy the code exactly as explained in the repository for basic use of argonaut:
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Create a parser with a program name and description const parser = try argsparse.newParser( allocator, "Argdemo", "Just a demo application", ); defer parser.deinit(); |
The only slightly more “complicated” part is if we want to create a validation function - very similar to what we have seen in previous chapters:
|
// create a function to validate the age const fn_validate_age = struct { // *const fn (args: []const []const u8) anyerror!void; fn _(args: []const []const u8) !void { // ... }._; var age_opts = argsparse.Options{ .validate = fn_validate_age }; |
When we run the program, we get:
|
$ zig build && zig-out/bin/argdemo --name Ziguana --age 140 |
|
usage: Argdemo [-a|--age <integer>] -n|--name "<value>" [-h|--help] Just a demo application Arguments: -a --age Your age as a number. Only accepted between 18 and 120. Default: 33 -n --name Your name -h --help Print help information |