Example of using a library: argonaut

for Zig 0.15.2 Buy

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

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

Creating a project: zig init
Chapter summary
© 2025 - 2026 Zen of Zig