Zig Language Overview
Zig 0.14 — systems language | no hidden control flow | no hidden allocations | no preprocessor | compile-time execution
Paradigm
Imperative, procedural, data-oriented. Static typing with inference, structural (duck) typing via comptime. Compilation via LLVM + self-hosted backend. No operator overloading, no exceptions, no RAII, no macros.
Types
| Category |
Types |
| Signed int |
i8 i16 i32 i64 i128 isize |
| Unsigned int |
u8 u16 u32 u64 u128 usize |
| Float |
f16 f32 f64 f128 |
| Bool |
bool |
| Void |
void |
| Never |
noreturn |
| Array |
[N]T |
| Slice |
[]T |
| Pointer |
*T *const T [*]T (many-item) [*c]T (C pointer) |
| Optional |
?T |
| Error union |
E!T |
| Struct |
struct { field: T } |
| Union |
union { field: T } (tagged) |
| Enum |
enum { variant } |
| Packed union/enum |
packed union packed enum |
| Vector |
@Vector(N, T) (SIMD) |
| Function |
fn(params) ReturnType |
| Opaque |
opaque {} |
| Comptime |
comptime T (compile-time known) |
| Any type |
anytype (comptime duck typing) |
| Any error |
anyerror |
| Type |
type (first-class type) |
| Void (zero bits) |
comptime void |
Declarations
| Construct |
Syntax |
Notes |
| Function |
fn name(params) ReturnType { body } |
|
| Variable |
const x = expr; |
Immutable |
| Mutable |
var x = expr; |
|
| Constant |
const NAME: T = expr; |
Known at comptime |
| Export |
export fn name() void { ... } |
Visible to linker |
| Extern |
extern fn name() void; |
From another object |
| Struct |
const T = struct { field: T }; |
|
| Enum |
const E = enum { variant }; |
|
| Union |
const U = union(enum) { field: T }; |
Tagged union |
| Error set |
const Errors = error { E1, E2 }; |
|
| Test |
test "name" { ... } |
|
| Comptime block |
comptime { ... } |
|
| Inline assembly |
asm volatile ("code"); |
|
| Import |
const std = @import("std"); |
|
Control Flow
| Construct |
Syntax |
Notes |
| If |
if (cond) { ... } else { ... } |
|
| If (expression) |
const x = if (cond) a else b; |
|
| If (optional) |
if (opt) |val| { ... } else { ... } |
Capture unwrap |
| If (error union) |
if (result) |val| { ... } else |err| { ... } |
|
| While |
while (cond) { ... } |
|
| While (optional) |
while (opt) |val| { ... } |
|
| While (error) |
while (result) |val| { ... } else |err| { ... } |
|
| For |
for (slice) |item| { ... } |
|
| For (index) |
for (slice, 0..) |item, i| { ... } |
|
| Switch |
switch (expr) { variant => body, else => body } |
Exhaustive |
| Break |
break; |
|
| Continue |
continue; |
|
| Return |
return expr; |
|
| Defer |
defer expr; |
Run at scope exit |
| Errdefer |
errdefer expr; |
Run on error |
| Catch |
result catch |err| { ... } |
Unwrap or handle |
Switch Features
- Exhaustiveness checked at compile time
- Multiple items per arm:
1, 2 => ...
- Range arms:
0...10 => ...
- Capture prong names:
inline else => \|tag\| ...
Operators
| Category |
Operators |
| Arithmetic |
+ - * / % |
| Wrapping |
+% -% *% |
| Saturation |
+| -| *| |
| Comparison |
== != < > <= >= |
| Logical |
and or ! |
| Bitwise |
>> << & | ^ ~ |
| Compound |
+= -= *= /%= &= |= ^= <<= >>= |
| Nullable |
orelse catch .? (optional unwrap) |
| Misc |
.. (range) ** (ptr deref multi) . (field) .* (ptr deref) |
| Address-of |
&expr |
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
comptime {
// Runs at compile time
@compileLog("building for", @tagName(builtin.cpu));
}
// Generics via comptime parameters
fn ArrayList(comptime T: type) type {
return struct {
items: []T,
fn init(self: *Self) void { ... }
};
}
Comptime Features
comptime blocks execute during compilation
comptime function parameters are compile-time known
inline for / inline while unroll loops at comptime
@Type(...) construct types at comptime
@fieldParentPtr reverse field lookup
@hasDecl @hasField compile-time reflection
@typeInfo get type information as a struct
Memory
| Operation |
Syntax |
Notes |
| Allocate |
allocator.alloc(T, n) |
Returns ![]T |
| Free |
allocator.free(slice) |
|
| Realloc |
allocator.realloc(slice, n) |
|
| Create |
allocator.create(T) |
Single element |
| Destroy |
allocator.destroy(ptr) |
|
| Allocator param |
allocator: Allocator |
Always explicit |
Allocators
std.heap.page_allocator std.heap.arena_allocator std.heap.FixedBufferAllocator std.heap.ArenaAllocator std.heap.StackFallbackAllocator std.heap.c_allocator
Zig's allocator philosophy
- Every allocation takes an explicit allocator parameter
- No global allocator, no implicit allocations
- Allocators compose (pool → arena → page)
Error Handling
// Error union type: ReturnType!Error
fn div(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return a / b;
}
// Try operator: early return on error
const result = try div(10, 2);
// Catch: provide default
const val = div(10, 0) catch 0;
// Global error set vs specific
fn parse() ParseError!i32 { ... }
Defer & Errdefer
{
const buf = try allocator.alloc(u8, 100);
defer allocator.free(buf); // always runs at scope exit
errdefer allocator.free(buf); // runs only if error returned
// ... use buf
} // defer triggers here
Standard Library (selected)
| Module |
Contents |
std.fs |
File, Dir, Path, cwd, readFile, writeFile, copyFile, deleteFile, makeDir, makePath |
std.io |
Reader, Writer, Buffered, formatting, printing |
std.net |
TcpStream, TcpListener, UdpSocket, IpAddress, dns |
std.mem |
copy, set, swap, indexOf, split, join, Allocator, Allocator interface |
std.process |
ChildProcess, Args, Env, exit, getSelfPath |
std.time |
Instant, nanoTime, sleep, Timer, epoch |
std.Thread |
spawn, Pool, Mutex, RwLock, Semaphore, Condition, Atomic |
std.math |
sqrt, sin, cos, tan, exp, log, pow, abs, min, max, clamp |
std.hash |
hash functions, crc32, sha256, sha512, md5 |
std.json |
parse, stringify, Value, Array, Object |
std.sort |
sort, binarySearch, insertion, merge, pdqsort |
std.unicode |
utf8, utf16, width, case mapping |
std.Build |
Build system (replaces make/cmake) |
std.Target |
Cross-compilation target detection |
std.debug |
assert, panic, print, dump, trace |
Testing
test "basic addition" {
try std.testing.expectEqual(4, add(2, 2));
try std.testing.expect(add(1, 1) == 2);
try std.testing.expectError(error.DivisionByZero, div(1, 0));
}
zig test file.zig # run tests
zig build test # run all tests in project
zig test --name "filter" # filter test names
Cross-Compilation
zig build-exe main.zig --target x86_64-windows # cross-compile to Windows
zig build-exe main.zig --target aarch64-linux # cross-compile to ARM64 Linux
zig build-exe main.zig --target riscv64-linux # cross-compile to RISC-V
Zig ships with libc headers and cross-compilation toolchains for all major targets — no separate cross-compiler needed.
C Interop
// Import C header
const c = @cImport({
@cInclude("stdio.h");
});
// Link C library
pub fn main() void {
_ = c.printf("hello %d\n", 42);
}
// Direct use of C ABI
extern "c" fn strlen(s: [*:0]u8) usize;
// Zig is a better C:
// - @cImport for headers
// - export fn for C-ABI exports
// - [*:0]T for null-terminated strings
// - @ptrCast for pointer conversion
// - packed struct for ABI-compatible layouts
Notable differences from C
- No preprocessor (use
comptime)
- No implicit casts (explicit
@intCast @floatCast)
- No UB by default (safety checks in debug, opt-in
@setRuntimeSafety)
defer replaces goto cleanup patterns
anytype replaces void* + macro generics
- Array type includes length (
[N]T vs T[N])
undefined instead of uninitialized
null of optional ?*T, not pointer
Compilation
zig build-exe main.zig # compile executable
zig build-lib lib.zig # compile library
zig build-obj obj.zig # compile object
zig run main.zig # compile + run
zig test file.zig # compile + run tests
zig fmt file.zig # format code
zig build # build project (build.zig)
zig translate-c foo.h # translate C header to Zig
Build Modes
| Mode |
Flag |
Safety checks |
Optimizations |
| Debug |
(default) |
Full |
None |
| ReleaseSafe |
-Doptimize=ReleaseSafe |
Full |
Speed |
| ReleaseFast |
-Doptimize=ReleaseFast |
None |
Speed |
| ReleaseSmall |
-Doptimize=ReleaseSmall |
None |
Size |