This appendix is a quick map of Zig syntax. It is not a grammar. The full Zig grammar is part of the official language reference. Zig 0.16 also keeps the language small enough...
This appendix is a quick map of Zig syntax. It is not a grammar. The full Zig grammar is part of the official language reference. Zig 0.16 also keeps the language small enough that the grammar remains practical to read directly.
A.1 Source Files
A Zig source file is a container. It contains declarations.
const std = @import("std");
const max_count = 100;
fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
std.debug.print("{d}\n", .{add(1, 2)});
}Declarations at file scope are order-independent.
pub fn main() void {
f();
}
fn f() void {}A.2 Comments
// ordinary comment
/// documentation comment for the next declaration
//! documentation comment for the current containerZig has no block comments.
A.3 Names
const name = value;
var count: usize = 0;A name may be public.
pub const version = 1;
pub fn run() void {}A name may use @"..." when it is not a normal identifier.
const @"type" = 123;A.4 Imports
const std = @import("std");
const math = @import("math.zig");An imported file is a struct-like namespace.
math.add(1, 2);A.5 Constants and Variables
const x = 10;
var y: i32 = 20;A const binding cannot be assigned again.
const x = 1;
// x = 2; // errorA var binding can be assigned.
var n: i32 = 0;
n = n + 1;A.6 Basic Types
bool
void
noreturn
type
comptime_int
comptime_float
u8 i8
u16 i16
u32 i32
u64 i64
u128 i128
usize isize
f16
f32
f64
f80
f128Integer types may also have explicit bit widths.
const small: u3 = 5;
const signed: i7 = -12;A.7 Literals
const a = 123;
const b = 0xff;
const c = 0b1010;
const d = 1.25;
const e = true;
const f = false;
const g = null;
const h = undefined;Character and string literals:
const ch = 'A';
const s = "hello";
const nl = '\n';Multiline strings use \\.
const text =
\\first line
\\second line
;A.8 Arrays
const a = [_]u8{ 1, 2, 3 };
const b: [3]u8 = .{ 1, 2, 3 };Indexing:
const x = a[0];Length:
const n = a.len;Sentinel array:
const msg: [5:0]u8 = "hello".*;A.9 Slices
const a = [_]u8{ 1, 2, 3, 4 };
const s = a[1..3];A slice has a pointer and a length.
s.ptr
s.lenOpen-ended slice:
const t = a[2..];A.10 Strings
A string literal is a pointer to constant bytes.
const s = "hello";Use {s} to print it.
std.debug.print("{s}\n", .{s});Zig strings are byte sequences. Text encoding is a library concern.
A.11 Pointers
Single-item pointer:
var x: i32 = 10;
const p: *i32 = &x;
p.* = 20;Pointer to constant data:
const p: *const i32 = &x;Many-item pointer:
const p: [*]u8 = buffer.ptr;Optional pointer:
var p: ?*Node = null;A.12 Structs
const Point = struct {
x: i32,
y: i32,
};Initialization:
const p = Point{ .x = 10, .y = 20 };Field access:
const x = p.x;Default field value:
const User = struct {
id: u64,
active: bool = true,
};Methods are functions inside a struct.
const Point = struct {
x: i32,
y: i32,
fn zero() Point {
return .{ .x = 0, .y = 0 };
}
};A.13 Enums
const Color = enum {
red,
green,
blue,
};Use:
const c = Color.red;Inferred enum value:
const c: Color = .red;Enum with integer tag type:
const Mode = enum(u8) {
read = 1,
write = 2,
};A.14 Unions
Plain union:
const Value = union {
i: i32,
f: f64,
};Tagged union:
const Token = union(enum) {
number: i64,
name: []const u8,
eof,
};Switch on a tagged union:
switch (tok) {
.number => |n| useNumber(n),
.name => |s| useName(s),
.eof => return,
}A.15 Optionals
var x: ?i32 = null;
x = 10;Unwrap with if.
if (x) |value| {
std.debug.print("{d}\n", .{value});
}Use orelse.
const value = x orelse 0;Force unwrap:
const value = x.?;Use force unwrap only when null would be a programmer error.
A.16 Errors
Error set:
const ParseError = error{
Empty,
InvalidDigit,
};Error union:
fn parse() ParseError!i32 {
return error.Empty;
}Return success:
return 123;Propagate error:
const n = try parse();Handle error:
const n = parse() catch 0;A.17 Functions
fn add(a: i32, b: i32) i32 {
return a + b;
}No return value:
fn clear() void {}Error return:
fn read() !usize {
return error.EndOfStream;
}Compile-time parameter:
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}A.18 Blocks
A block groups statements.
{
const x = 1;
_ = x;
}A labeled block can yield a value.
const x = blk: {
const a = 10;
break :blk a + 1;
};A.19 if
if (x > 0) {
positive();
} else {
nonPositive();
}if is an expression.
const sign = if (x < 0) -1 else 1;Optional capture:
if (maybe) |value| {
use(value);
} else {
missing();
}Error union capture:
if (parse()) |value| {
use(value);
} else |err| {
useError(err);
}A.20 switch
switch (x) {
0 => zero(),
1 => one(),
else => other(),
}Multiple values:
switch (ch) {
'a', 'e', 'i', 'o', 'u' => vowel(),
else => consonant(),
}Range:
switch (n) {
0...9 => digit(),
else => other(),
}Capture:
switch (tok) {
.number => |n| use(n),
else => {},
}A.21 while
var i: usize = 0;
while (i < 10) {
i += 1;
}Continue expression:
var i: usize = 0;
while (i < 10) : (i += 1) {
use(i);
}Optional loop:
while (next()) |item| {
use(item);
}Error union loop:
while (next()) |item| {
use(item);
} else |err| {
useError(err);
}A.22 for
for (items) |item| {
use(item);
}Index:
for (items, 0..) |item, i| {
use(i, item);
}Mutable pointer iteration:
for (&items) |*item| {
item.* += 1;
}Multiple sequences:
for (a, b) |x, y| {
use(x, y);
}A.23 break and continue
while (true) {
break;
}while (condition()) {
continue;
}Labeled loop:
outer: while (true) {
while (true) {
break :outer;
}
}A.24 defer and errdefer
defer runs at scope exit.
{
lock();
defer unlock();
work();
}errdefer runs only when the scope returns an error.
fn create() !*Thing {
const p = try allocThing();
errdefer freeThing(p);
try initThing(p);
return p;
}A.25 comptime
A comptime value is known during compilation.
fn Vec(comptime T: type, comptime n: usize) type {
return struct {
data: [n]T,
};
}Use:
const V3 = Vec(f32, 3);Compile-time block:
comptime {
_ = @import("std");
}A.26 Inline Loops
inline for (.{ u8, u16, u32 }) |T| {
_ = T;
}inline unrolls the loop at compile time.
A.27 Anonymous Struct and Tuple Literals
Struct literal with known type:
const p: Point = .{ .x = 1, .y = 2 };Tuple literal:
const args = .{ 1, "hello", true };Format arguments are commonly passed this way.
std.debug.print("{d} {s}\n", .{ 10, "ok" });A.28 Operators
Arithmetic:
+ - * / %Assignment:
= += -= *= /= %=Comparison:
== != < <= > >=Boolean:
and or !Bitwise:
& | ^ ~ << >>Pointer and field:
&x
p.*
x.yOptional and error:
x orelse y
try f()
f() catch yA.29 Builtin Functions
Builtin functions begin with @.
@import("std")
@This()
@TypeOf(x)
@sizeOf(T)
@alignOf(T)
@intCast(x)
@as(T, x)
@ptrCast(p)
@alignCast(p)
@panic("message")A builtin is part of the language, not a normal library function.
A.30 Tests
const std = @import("std");
test "addition" {
try std.testing.expect(1 + 1 == 2);
}Run tests:
zig test file.zigA test block may use any code allowed in a function body.
A.31 unreachable
switch (x) {
0 => zero(),
1 => one(),
else => unreachable,
}unreachable states that control cannot reach that point. Reaching it is a bug.
A.32 undefined
var x: i32 = undefined;undefined gives a value no defined contents. It is used when the program will write the value before reading it.
var buf: [1024]u8 = undefined;Reading undefined memory is a bug.
A.33 pub, extern, export, inline, noinline
pub fn f() void {}extern fn puts(s: [*:0]const u8) c_int;export fn add(a: i32, b: i32) i32 {
return a + b;
}inline fn small() void {}
noinline fn large() void {}A.34 Container Declarations
A container may be a file, struct, enum, union, or opaque type.
const S = struct {
const Self = @This();
value: i32,
fn get(self: Self) i32 {
return self.value;
}
};Declarations inside a container are accessed with dot syntax.
S.getA.35 Opaque Types
const Handle = opaque {};An opaque type has unknown layout. It is useful for handles from C or private implementation details.
A.36 Packed and Extern Layout
Packed struct:
const Flags = packed struct {
a: bool,
b: bool,
c: u6,
};Extern struct:
const CPoint = extern struct {
x: c_int,
y: c_int,
};Use extern for C ABI layout. Use packed for bit-level layout.
A.37 Common Program Shape
const std = @import("std");
pub fn main() !void {
const allocator = std.heap.page_allocator;
var list = std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append('A');
std.debug.print("{c}\n", .{list.items[0]});
}This shows the usual parts: import, entry point, allocator, cleanup, error propagation, and printing.