14 KiB
14 KiB
Zig Language Basics Reference (Zig 0.16.0)
Core language features, control flow, and type system fundamentals.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
Key 0.16 language changes:
@Typeremoved; use specific type-construction builtins.@cImportis deprecated as the long-term C translation API; prefer build-system translation.- Runtime vector indexes are forbidden.
- Vectors and arrays no longer support in-memory coercion.
- Returning the address of an expired local is diagnosed.
- Packed structs/unions cannot contain pointers.
- Packed unions and extern packed/enum layouts require more explicit backing types.
- Type resolution is lazier in some cases and stricter in others; investigate new dependency-loop diagnostics.
Table of Contents
- Types
- Control Flow
- Error Handling
- Optionals
- Structs
- Enums
- Unions
- Pointers and Slices
- Comptime
- Functions
Types
Primitive Types
// Integers (signed and unsigned, any bit width 1-65535)
i8, i16, i32, i64, i128, isize // signed
u8, u16, u32, u64, u128, usize // unsigned
i7, u24, i53 // arbitrary widths
// Floats
f16, f32, f64, f80, f128
// Other
bool // true or false
void // zero-size type
noreturn // function never returns
type // type of types (comptime only)
anyopaque // type-erased pointer target
comptime_int // arbitrary precision integer (comptime only)
comptime_float // arbitrary precision float (comptime only)
// C interop types
c_char, c_short, c_int, c_long, c_longlong
c_ushort, c_uint, c_ulong, c_ulonglong
c_longdouble
Type Coercion
// Implicit coercions (safe, automatic)
const a: u16 = 42; // comptime_int → u16
const b: i32 = a; // u16 → i32 (widening)
const c: f64 = 3.14; // comptime_float → f64
const d: []const u8 = "hello"; // *const [5:0]u8 → []const u8
const e: ?i32 = 5; // i32 → ?i32
// @as for explicit safe coercion
const x = @as(u32, 100);
// Casts for unsafe/reinterpret conversions
const y: u8 = @intCast(big_value); // may panic if value doesn't fit
const z: u32 = @bitCast(float_val); // reinterpret bits
Arrays
// Fixed-size arrays
const arr: [5]u8 = .{ 1, 2, 3, 4, 5 };
const arr2 = [_]u8{ 1, 2, 3 }; // infer length
const zeros = [_]u8{0} ** 100; // repeat pattern
// Sentinel-terminated arrays
const str: [5:0]u8 = "hello".*; // null-terminated
const arr: [3:255]u8 = .{ 1, 2, 3 }; // 255-terminated
// Access
const elem = arr[2];
const len = arr.len;
// Iteration
for (arr) |elem| { ... }
for (arr, 0..) |elem, i| { ... } // with index
for (&arr) |*elem| { elem.* = 0; } // mutable
Tuples
const tuple = .{ 42, "hello", true };
const first = tuple[0]; // 42
const len = tuple.len; // 3
// Destructuring
const a, const b, const c = tuple;
Control Flow
if
// Basic
if (condition) {
// ...
} else if (other) {
// ...
} else {
// ...
}
// Expression form
const value = if (condition) x else y;
// With optionals
if (optional_value) |unwrapped| {
// unwrapped is non-null
} else {
// was null
}
// With error unions
if (error_union) |value| {
// success
} else |err| {
// handle err
}
switch
const result = switch (value) {
1 => "one",
2, 3 => "two or three",
4...10 => "four to ten",
else => "other",
};
// Capture
switch (tagged_union) {
.variant => |payload| { ... },
.other => |*ptr| { ptr.* = new_value; }, // mutable capture
}
// Comptime switch on types
switch (@typeInfo(T)) {
.int => |info| { ... },
.float => { ... },
else => @compileError("unsupported type"),
}
Labeled switch (0.14.0+) - State Machines
state: switch (initial_state) {
.idle => {
continue :state .running; // transition
},
.running => {
if (done) break :state result; // exit with value
continue :state .running; // loop
},
.error => return error.Failed,
}
Non-exhaustive enum switch (0.15.x)
switch (non_exhaustive_enum) {
.known_a => {},
.known_b => {},
else => {}, // other named tags
_ => {}, // unnamed integer values
}
while
// Basic
while (condition) { ... }
// With else (runs if condition was never true or on break)
while (condition) { ... } else { ... }
// With continue expression
var i: usize = 0;
while (i < 10) : (i += 1) { ... }
// With optional
while (iterator.next()) |item| { ... }
// With error union
while (reader.readByte()) |byte| {
...
} else |err| {
if (err != error.EndOfStream) return err;
}
// Infinite loop
while (true) { ... }
for
// Iterate slice/array
for (items) |item| { ... }
// With index
for (items, 0..) |item, i| { ... }
// Multiple sequences (must have same length)
for (a, b, c) |x, y, z| { ... }
// Mutable iteration
for (&items) |*item| { item.* = new_value; }
// Range (comptime only for runtime, but works in comptime blocks)
inline for (0..10) |i| { ... }
Labels and Control
// Labeled blocks
const result = blk: {
if (condition) break :blk value;
break :blk other_value;
};
// Labeled loops
outer: for (rows) |row| {
for (row) |cell| {
if (cell == target) break :outer;
}
}
// continue with label
outer: for (items) |item| {
for (sub_items) |sub| {
if (skip) continue :outer;
}
}
defer / errdefer
// Always runs when scope exits
fn example() void {
const resource = acquire();
defer release(resource); // runs on return
// use resource...
}
// Only runs on error return
fn example() !void {
const ptr = try allocate();
errdefer free(ptr); // runs only if function returns error
try doSomething(ptr);
return ptr; // errdefer does NOT run
}
// errdefer with capture
fn example() !void {
errdefer |err| {
log.err("Failed with: {}", .{err});
};
try riskyOperation();
}
Error Handling
Error Sets
// Define error set
const FileError = error{
NotFound,
AccessDenied,
OutOfMemory,
};
// Inferred error set (use sparingly)
fn foo() !void { ... } // error set inferred from body
// Merge error sets
const AllErrors = FileError || NetworkError;
// anyerror - global error set (avoid when possible)
fn bar() anyerror!void { ... }
Error Unions
// Error union type: ErrorSet!PayloadType
fn parse(s: []const u8) ParseError!u32 { ... }
fn read() ![]u8 { ... } // inferred error set
// Return errors
return error.InvalidInput;
// Return success
return value;
Handling Errors
// try - propagate error, unwrap on success
const value = try mayFail();
// catch - provide default on error
const value = mayFail() catch 0;
const value = mayFail() catch |err| {
log.err("failed: {}", .{err});
return default;
};
// catch unreachable - assert no error (crashes if error)
const value = mayFail() catch unreachable;
// if with error union
if (mayFail()) |value| {
// success
} else |err| {
// handle error
}
// switch on specific errors
mayFail() catch |err| switch (err) {
error.NotFound => return null,
error.AccessDenied => return error.PermissionDenied,
else => return err,
};
Optionals
Optional Types
// Optional type: ?T
var maybe: ?i32 = null;
maybe = 42;
// Check for null
if (maybe != null) { ... }
if (maybe == null) { ... }
Unwrapping
// orelse - default value
const value = maybe orelse 0;
const value = maybe orelse return error.Missing;
const value = maybe orelse unreachable; // assert non-null
// .? - assert and unwrap (crashes on null)
const value = maybe.?;
// if unwrap
if (maybe) |value| {
// value is non-null
} else {
// was null
}
// while unwrap
while (iterator.next()) |item| { ... }
Optional Pointers
// ?*T has null representation as 0 (same size as *T)
var ptr: ?*Node = null;
ptr = &node;
if (ptr) |p| {
p.*.data = 42;
}
Structs
Basic Structs
const Point = struct {
x: f32,
y: f32,
// Methods
pub fn distance(self: Point, other: Point) f32 {
const dx = self.x - other.x;
const dy = self.y - other.y;
return @sqrt(dx * dx + dy * dy);
}
// Static method
pub fn origin() Point {
return .{ .x = 0, .y = 0 };
}
};
// Usage
const p = Point{ .x = 1.0, .y = 2.0 };
const p2: Point = .{ .x = 3.0, .y = 4.0 }; // type inferred
const dist = p.distance(p2);
Default Values
const Config = struct {
name: []const u8,
port: u16 = 8080, // default value
debug: bool = false,
};
const cfg: Config = .{ .name = "server" }; // uses defaults
@This() for Self-Reference
const Node = struct {
const Self = @This();
next: ?*Self = null,
data: i32,
pub fn append(self: *Self, node: *Self) void {
self.next = node;
}
};
Packed Structs
const Flags = packed struct {
enabled: bool, // 1 bit
mode: u2, // 2 bits
_reserved: u5, // 5 bits
}; // Total: 1 byte
const flags: Flags = @bitCast(@as(u8, 0b10100001));
Extern Structs (C ABI)
const CStruct = extern struct {
x: c_int,
y: c_int,
};
Enums
Basic Enums
const Color = enum {
red,
green,
blue,
};
const c: Color = .red;
// Switch (must be exhaustive)
switch (c) {
.red => {},
.green => {},
.blue => {},
}
Enums with Values
const HttpStatus = enum(u16) {
ok = 200,
not_found = 404,
internal_error = 500,
_, // non-exhaustive marker
};
const code: u16 = @intFromEnum(HttpStatus.ok); // 200
const status: HttpStatus = @enumFromInt(404); // .not_found
Enum Methods
const Direction = enum {
north,
south,
east,
west,
pub fn opposite(self: Direction) Direction {
return switch (self) {
.north => .south,
.south => .north,
.east => .west,
.west => .east,
};
}
};
Unions
Tagged Unions
const Value = union(enum) {
int: i64,
float: f64,
string: []const u8,
none, // void payload
pub fn isNumeric(self: Value) bool {
return switch (self) {
.int, .float => true,
else => false,
};
}
};
const v: Value = .{ .int = 42 };
switch (v) {
.int => |n| std.debug.print("{}", .{n}),
.float => |f| std.debug.print("{}", .{f}),
.string => |s| std.debug.print("{s}", .{s}),
.none => {},
}
Bare Unions (no tag)
const Bare = union {
int: i32,
float: f32,
};
// Must track active field manually - unsafe
Extern Unions (C ABI)
const CUnion = extern union {
as_int: c_int,
as_float: f32,
};
Pointers and Slices
Pointer Types
*T // single-item pointer
*const T // pointer to const
[*]T // many-item pointer (unknown length)
[*:0]T // null-terminated many-item pointer
?*T // optional pointer
// Alignment
*align(16) T // pointer with explicit alignment
Slices
[]T // slice (pointer + length)
[]const T // slice to const data
[:0]T // null-terminated slice
// Create slice from array
const arr = [_]u8{ 1, 2, 3, 4, 5 };
const slice: []const u8 = &arr;
const sub: []const u8 = arr[1..4]; // {2, 3, 4}
// Slice operations
const len = slice.len;
const ptr = slice.ptr; // [*]const u8
const elem = slice[2];
Pointer Arithmetic
// Many-item pointers support arithmetic
const ptr: [*]u8 = buffer.ptr;
const next = ptr + 1;
const offset = ptr + n;
// Single-item pointers do NOT support arithmetic
// Use slicing instead:
const slice = ptr[0..n];
Sentinel-Terminated
// Null-terminated string
const str: [:0]const u8 = "hello";
const c_str: [*:0]const u8 = str.ptr;
// Custom sentinel
const arr: [3:255]u8 = .{ 1, 2, 3 }; // followed by 255
Comptime
Comptime Variables
comptime var count: u32 = 0;
// Comptime block
comptime {
count += 1;
}
// Comptime parameter
fn repeat(comptime n: usize, value: u8) [n]u8 {
return [_]u8{value} ** n;
}
Comptime Functions
fn factorial(comptime n: u32) u32 {
if (n == 0) return 1;
return n * factorial(n - 1);
}
const result = factorial(5); // computed at compile time
Type as First-Class Value
fn Container(comptime T: type) type {
return struct {
items: []T,
pub fn get(self: @This(), i: usize) T {
return self.items[i];
}
};
}
const IntContainer = Container(i32);
@typeInfo
fn isInteger(comptime T: type) bool {
return @typeInfo(T) == .int;
}
fn fieldNames(comptime T: type) []const []const u8 {
const info = @typeInfo(T);
if (info != .@"struct") @compileError("expected struct");
var names: [info.@"struct".fields.len][]const u8 = undefined;
for (info.@"struct".fields, 0..) |field, i| {
names[i] = field.name;
}
return &names;
}
inline for/while
// Unroll loop at compile time
inline for (0..4) |i| {
array[i] = computeValue(i);
}
// Generate code for each field
inline for (std.meta.fields(T)) |field| {
@field(value, field.name) = default;
}
Functions
Function Basics
fn add(a: i32, b: i32) i32 {
return a + b;
}
// With error
fn parse(s: []const u8) !i32 { ... }
// Void return
fn log(msg: []const u8) void { ... }
// Noreturn
fn abort() noreturn {
@panic("aborted");
}
Generic Functions
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// Using anytype
fn print(value: anytype) void {
const T = @TypeOf(value);
// ...
}
Function Pointers
const BinaryOp = *const fn (i32, i32) i32;
fn apply(op: BinaryOp, a: i32, b: i32) i32 {
return op(a, b);
}
Calling Conventions
fn cFunc() callconv(.c) void { ... }
fn nakedFunc() callconv(.naked) noreturn { ... }
fn inlineFunc() callconv(.@"inline") i32 { ... }
Export/Extern
// Export to C
export fn my_function() void { ... }
// Import from C
extern "c" fn printf(fmt: [*:0]const u8, ...) c_int;
// Link with library
extern "SDL2" fn SDL_Init(flags: u32) c_int;
Inline Functions
inline fn fastAdd(a: i32, b: i32) i32 {
return a + b;
}
// Forces inlining - compile error if impossible