I spent three years writing TUI tools in C. I’m not going back. Zig gives me everything C does — direct memory control, no runtime, single binary output — plus a build system that doesn’t make me want to quit programming.
what I was doing in C
My TUI toolkit was about 4k lines of C spread across a handful of
tools: a hex viewer, a log tailer, and a process inspector. They shared
a small rendering layer I called termbox.c — basically a manual
implementation of the parts of ncurses I actually used.
The pain points were all in the build system and the allocator story.
make is fine until you have cross-compilation targets, and tracking
allocations manually across tools that share a library got old fast.
what Zig gives me instead
Comptime replaces most of my C preprocessor usage. Instead of
#define COLS 80 and a thousand #ifdef PLATFORM_LINUX guards, I
write:
const platform = @import("builtin").os.tag;
const default_cols: u16 = if (platform == .windows) 120 else 80;
The allocator pattern means I can write a function once and the caller decides whether it heap-allocates, arena-allocates, or uses a fixed buffer:
pub fn renderLine(buf: []u8, allocator: std.mem.Allocator, row: Row) ![]u8 {
var out = std.ArrayList(u8).init(allocator);
// ...
return out.toOwnedSlice();
}
zig build is just a Zig program. Cross-compiling to
aarch64-linux-musl is one flag.
what I gave up
Error messages. Zig’s are improving but a complex comptime failure still produces output that requires two cups of coffee and a quiet room to decode. C’s errors are worse on average but the ceiling is lower.
The migration took about two weekends. The resulting tools are marginally slower to compile and significantly easier to maintain. Good trade.