Created
August 16, 2025 01:53
-
-
Save clifinger/b681f5f29ad9b4761dbeb4f76bb76c00 to your computer and use it in GitHub Desktop.
Zig: Handling CLI Arguments with a Debug/Release Allocator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// | |
/// A robust pattern for initializing a Command-Line (CLI) application in Zig. | |
/// | |
/// This Gist demonstrates the idiomatic way to handle program arguments by | |
/// intelligently selecting a memory allocator at compile-time based on the | |
/// build mode. | |
/// | |
/// Key Concepts Shown: | |
/// | |
/// 1. Compile-Time Allocator Selection: | |
/// - In Debug/ReleaseSafe builds, it uses `std.heap.DebugAllocator` to | |
/// automatically detect memory leaks. | |
/// - In ReleaseFast/ReleaseSmall builds, it uses `std.heap.smp_allocator` | |
/// for maximum performance. | |
/// | |
/// 2. Labeled Blocks: | |
/// The `gpa: { ... }` syntax is used to cleanly initialize multiple | |
/// constants (`gpa` and `is_debug`) from a single expression. | |
/// | |
/// 3. Guaranteed Cleanup with `defer`: | |
/// Ensures that memory allocated for arguments is always freed and that the | |
/// DebugAllocator is properly de-initialized, even if errors occur. | |
/// | |
const std = @import("std"); | |
const builtin = @import("builtin"); | |
// We create a DebugAllocator instance that will be used in debug builds. | |
// Its job is to wrap another allocator and check for leaks. | |
var debug_allocator: std.heap.DebugAllocator(.{}) = .init{}; | |
pub fn main() !void { | |
// This is the core of the pattern: a compile-time allocator selection. | |
// We use a labeled block to initialize two constants at once: | |
// - gpa: The General Purpose Allocator for our application. | |
// - is_debug: A boolean flag we can use for conditional logic. | |
const gpa, const is_debug = gpa: { | |
break :gpa switch (builtin.mode) { | |
// In debug/safe modes, wrap our allocator in the leak checker. | |
.Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, | |
// In release modes, use a high-performance allocator directly. | |
// `smp_allocator` is a good general-purpose, thread-safe choice. | |
.ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, | |
}; | |
}; | |
// This deferred statement ensures our resources are cleaned up before main exits. | |
// The `if` is crucial: we only deinit the DebugAllocator if we actually used it. | |
// At the end of its life, it will check for any memory leaks. | |
defer if (is_debug) { | |
_ = debug_allocator.deinit(); | |
}; | |
// Allocate a slice for the command-line arguments using our chosen allocator (gpa). | |
const args = try std.process.argsAlloc(gpa); | |
// No matter what happens next, we schedule the arguments' memory to be freed. | |
// It's crucial to use the *same allocator* for freeing as for allocating. | |
defer std.process.argsFree(gpa, args); | |
// Print some info to demonstrate which mode is active. | |
// Try running with: `zig build run` and `zig build run -Drelease-fast` | |
std.debug.print("Debug allocator in use: {}\n", .{is_debug}); | |
std.debug.print("Executing in mode: {s}\n", .{@tagName(builtin.mode)}); | |
// A small piece of application logic to demonstrate using the parsed arguments. | |
if (args.len > 1) { | |
std.debug.print("Changing directory to: {s}\n", .{args[1]}); | |
// Note: Using `chdir` might fail. A real app would handle the error. | |
_ = std.os.linux.chdir(args[1]); | |
} else { | |
std.debug.print("No directory argument provided.\n", .{}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment