The competition for low-level systems programming
Zig and Rust offer divergent philosophies for systems programming, with Zig prioritizing transparency and Rust focusing on memory safety. Developers face technical hurdles when attempting to compile the Linux kernel version 6.1.6 using zig cc.
Divergent philosophies in memory management
Rust relies on its borrow checker, lifetimes, and a rich type system to enforce memory safety and zero-cost abstractions without garbage collection. Zig targets the C replacement role by prioritizing simplicity and transparency. Zig removes hidden control flow and memory magic to ensure developers know exactly what the compiler performs. While Rust developers rely on a borrow checker and a complex type system to ensure memory safety, Zig proponents prioritize a minimalist approach that removes hidden control flow to ensure the compiler remains predictable and transparent during low-level execution. Zig aims to be so practical that developers use it even if they dislike it.
The rivalry between these languages shapes the future of performance-critical and maintainable software. Rust works to provide ironclad safety guarantees. Zig focuses on being a predictable and debuggable tool.
Obstacles in Linux kernel compilation
Compiling the Linux kernel with zig cc presents significant technical hurdles for developers. Users attempting to use make CC='zig cc' with kernel version 6.1.6 encounter the error unknown target CPU 'generic'. This error appears in the scripts/Makefile.build file. The toolchain also reports this linker is not supported when processing Kconfig.include. To manage these issues, developers must create alias scripts for zig cc, zig objcopy, zig ar, and zig cxx. You know how much effort manual toolchain aliasing requires when a compiler lacks built-in tools like readelf, objdump, and strip.
One workaround involves modifying the Linux source tree to replace instances of -soname with -install_name and adjusting linker flag passing to use -Wl instead of passing flags directly. Another developer noted that zig cc ignores the -o flag if the -MMD flag is set. In the context of cross-compiling nixpkgs, the zig2nix project shows that libffi compiles but its tests fail. One user attempted to use bear -- make with multiple manual exports for LD, AR, NM, STRIP, OBJCOPY, OBJDUMP, OBJSIZE, READELF, HOSTCC, HOSTCXX, HOSTAR, and HOSTLD to bypass initial errors.
The discussion around Linux kernel maintenance often focuses on the influx of code. Linus Torvalds observed that some release candidates reach a "new normal" of large updates. This trend persists as developers express concern regarding the maintenance load of reviewing massive amounts of code. Some maintainers worry that large volumes of submissions increase the risk of obfuscated bugs or malicious code being introduced into the kernel. Will the increasing volume of code submissions eventually force the kernel to adopt automated AI agents for patch verification?
Pointer reform and implementation details
Zig 0.3.0 introduced Pointer Reform to resolve ambiguities found in previous versions. This change replaced the single & operator with distinct syntax for addresses and pointers. The modification addressed a flaw in version 0.2.0 where the & operator behaved differently for types than for other types.
| Type | Description | Syntax |
|---|---|---|
| Single-item pointer | Pointer to exactly one item | *T |
| Unknown-length pointer | Pointer to unknown number of items | [*]T |
| Array pointer | Pointer to N items | [N]T |
| Slice | Pointer to runtime-known items | []T |
The single-item pointer *T supports dereferencing with ptr.*. The unknown-length pointer [*]T supports indexing with ptr[i] and slicing with ptr[start..end]. Because the slice []T provides indexing and slicing but requires the type T to have a known size, it remains distinct. This change also replaced the ?? operator with the orelse keyword to ensure all control flow uses keywords.
The zig fmt tool uses the standard library’s event-based I/O abstractions and async/await syntax, which makes the tool multi-threaded with non-blocking I/O. A debug build of zig fmt on a laptop can format the entire Zig standard library in 2.1 seconds, which equals 75,516 lines per second. Error return traces in async/await functions work across suspend points, providing both an error message and a stack trace. The implementation of std.debug.dumpStackTrace and related functions uses an ArenaAllocator backed by DirectAllocator, which causes the tool to fail when the system runs out of memory. Will the development of a dedicated C pointer type resolve the friction in C API interactions?