Appendix A. Zig Syntax Summary
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...
Appendix A. Zig Syntax Summary
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 container
Zig 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; // error
A 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
f128
Integer 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.len
Open-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.y
Optional and error:
x orelse y
try f()
f() catch y
A.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.zig
A 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.get
A.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.