Zig 是一门现代化的系统级编程语言,由 Andrew Kelley 创建。它的设计理念强调显式优于隐式编译时代码执行零成本抽象以及与 C 语言的完美互操作性。本文将全面介绍 Zig 的基础语法。

本文示例基于 Zig 0.13.x,个别在后续版本中更名的 API 会以注释标出。

变量与常量

var 与 const

Zig 使用 var 声明可变变量,使用 const 声明常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const std = @import("std");

pub fn main() void {
// 可变变量
var x: i32 = 10;
x = 20; // 可以修改

// 常量
const y: i32 = 30;
// y = 40; // 编译错误:不能修改常量

// 类型推断:字面量默认是 comptime_int
var z = 50;
}

基本数据类型

Zig 的数值类型命名即位宽,规则统一、一目了然:

类别类型说明
有符号整数i8 i16 i32 i64 i1288~128 位
无符号整数u8 u16 u32 u64 u1288~128 位
任意位宽整数i7 u1 u20Zig 特有,位宽可任意指定
指针大小整数usize isize随平台指针位宽变化,常用于索引
浮点数f16 f32 f64 f80 f128IEEE 754
布尔bool只有 true / false
编译期数值comptime_int comptime_float数值字面量的默认类型
1
2
3
4
5
const a: i32 = 100000;
const b: u8 = 255;
const pi: f64 = 3.14159265359;
const flag: bool = true;
const ch: u21 = '中'; // UTF-8 码点

undefined:显式的未初始化

Zig 不允许「先声明、忘了初始化」,未初始化必须显式写出:

1
2
var x: i32 = undefined;  // 显式标记为未定义
x = 10; // 使用前必须赋值

数组与切片

数组

数组是固定大小的同类型元素集合,长度是类型的一部分:

1
2
3
4
5
6
7
8
9
10
11
// 固定大小数组
const arr = [5]i32{ 1, 2, 3, 4, 5 };

// 使用 _ 让编译器推断大小
const arr2 = [_]i32{ 1, 2, 3 };

// 重复初始化
const zeros = [_]i32{0} ** 10; // 10 个 0

// 长度是编译期已知的
const len = arr.len; // 5

切片

切片是「指针 + 长度」的视图,是对一段连续内存的引用:

1
2
3
4
5
6
7
8
9
10
var arr = [_]i32{ 1, 2, 3, 4, 5 };

// 创建切片(左闭右开)
const slice = arr[1..4]; // 元素 2, 3, 4

// 通过切片可以修改原数组
slice[0] = 100; // arr 变为 { 1, 100, 3, 4, 5 }

// 字符串字面量就是 u8 常量切片
const str: []const u8 = "Hello, Zig!";

多维数组

1
2
3
4
5
const matrix = [3][3]i32{
[_]i32{ 1, 2, 3 },
[_]i32{ 4, 5, 6 },
[_]i32{ 7, 8, 9 },
};

指针

Zig 的指针按「指向多少个元素、是否可为空、是否有哨兵」严格区分:

语法名称说明
*T单项指针指向单个值,不可为 null,不支持指针算术
[*]T多项指针指向未知长度的连续内存,边界由调用者保证
[]T切片指针 + 长度,日常最常用
[*:0]T哨兵指针以 0 结尾,兼容 C 字符串
[*c]TC 指针与 C 指针完全等价,允许 null 和算术,仅用于 FFI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var x: i32 = 42;
const ptr: *i32 = &x; // 单项指针
ptr.* = 100; // 解引用

// 可选指针:可以为 null
const maybe_ptr: ?*i32 = &x;

// 切片指针
var arr = [_]i32{ 1, 2, 3, 4, 5 };
const slice: []i32 = &arr; // 可变切片
const const_slice: []const i32 = &arr; // 常量切片

// 哨兵切片(以 0 结尾)
const str: [:0]const u8 = "hello";

与 C 互操作时会用到 [*c]T

1
extern fn c_function(ptr: [*c]const u8) void;

可选类型(Optionals)

Zig 没有 null 引用,用 ?T 显式表达「可能没有值」:

1
2
3
4
5
6
7
8
9
10
11
12
13
const maybe_number: ?i32 = 42;
const no_number: ?i32 = null;

// if 解构:有值则解包
if (maybe_number) |n| {
std.debug.print("Number: {}\n", .{n});
}

// orelse 提供默认值
const value = maybe_number orelse 0;

// .? 断言非空:为 null 时触发安全检查,慎用
const definite = maybe_number.?;

可选类型还能配合 while 实现迭代器模式(iter 需实现返回 ?Tnext 方法):

1
2
3
while (iter.next()) |value| {
std.debug.print("{} ", .{value});
}

控制流

if / else if / else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const x: i32 = 10;

if (x > 5) {
std.debug.print("x is greater than 5\n", .{});
} else if (x == 5) {
std.debug.print("x equals 5\n", .{});
} else {
std.debug.print("x is less than 5\n", .{});
}

// if 是表达式,可以直接求值
const y = if (x > 5) 100 else 0;

// 对可选类型使用 if 解构
const maybe_value: ?i32 = 42;
if (maybe_value) |value| {
std.debug.print("Value: {}\n", .{value});
} else {
std.debug.print("No value\n", .{});
}

while 与 for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// while 循环,冒号后是每次迭代末尾执行的表达式
var i: i32 = 0;
while (i < 5) : (i += 1) {
std.debug.print("{} ", .{i});
}

// continue 也会执行迭代表达式
var j: i32 = 0;
while (j < 10) : (j += 2) {
if (j == 4) continue;
std.debug.print("{} ", .{j});
}

// for 遍历数组、切片
const arr = [_]i32{ 1, 2, 3, 4, 5 };
for (arr) |elem| {
std.debug.print("{} ", .{elem});
}

// 带索引的 for
for (arr, 0..) |elem, idx| {
std.debug.print("[{}] = {}\n", .{ idx, elem });
}

// 带标签的循环:break/continue 到外层
outer: for (0..3) |a| {
for (0..3) |b| {
if (a + b == 3) break :outer;
}
}

switch:必须穷尽所有分支

Zig 的 switch 是详尽的(exhaustive),编译器会检查遗漏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const number = 5;

const result = switch (number) {
1 => "one",
2 => "two",
3 => "three",
4...10 => "between 4 and 10", // 范围匹配
else => "other",
};

// switch 与枚举配合时,else 可省略(编译器已确认穷尽)
const Color = enum { red, green, blue };
const color = Color.red;

const color_name = switch (color) {
.red => "Red",
.green => "Green",
.blue => "Blue",
};

函数

函数定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基本函数
fn add(a: i32, b: i32) i32 {
return a + b;
}

// 无返回值
fn printHello() void {
std.debug.print("Hello!\n", .{});
}

// 多返回值:返回匿名结构体
fn divide(dividend: i32, divisor: i32) struct { quotient: i32, remainder: i32 } {
return .{
.quotient = @divTrunc(dividend, divisor),
.remainder = @rem(dividend, divisor),
};
}

递归函数

1
2
3
4
fn factorial(n: u32) u32 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

函数指针

1
2
3
4
5
6
7
8
9
10
11
12
const MathOp = fn (a: i32, b: i32) i32;

fn multiply(x: i32, y: i32) i32 {
return x * y;
}

fn calculate(a: i32, b: i32, op: MathOp) i32 {
return op(a, b);
}

// 使用
const result = calculate(5, 3, multiply); // 15

结构体

基本结构体与方法

结构体通过「第一个参数为 self」的命名空间函数实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Point = struct {
x: f32,
y: f32,

pub fn distance(self: Point, other: Point) f32 {
const dx = self.x - other.x;
const dy = self.y - other.y;
return std.math.sqrt(dx * dx + dy * dy);
}
};

const p1 = Point{ .x = 0, .y = 0 };
const p2 = Point{ .x = 3, .y = 4 };
const dist = p1.distance(p2); // 5.0

默认字段值

1
2
3
4
5
6
7
8
const Config = struct {
host: []const u8 = "localhost",
port: u16 = 8080,
debug: bool = false,
};

const default_config = Config{}; // 使用所有默认值
const custom_config = Config{ .port = 3000 }; // 只覆盖指定字段

自引用结构体

1
2
3
4
const Node = struct {
value: i32,
next: ?*Node = null,
};

枚举与联合体

枚举(enum)

1
2
3
4
5
6
7
8
9
10
11
const Status = enum {
pending,
running,
completed,
failed,

// 枚举也可以有方法
pub fn isDone(self: Status) bool {
return self == .completed or self == .failed;
}
};

联合体(union)

普通 union 不记录当前激活的字段,读取错误字段是未定义行为;union(enum) 是带标签的安全联合体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 普通联合体:不安全,编译器不跟踪激活字段
const Value = union {
int: i32,
float: f64,
boolean: bool,
};

var v = Value{ .int = 42 };
// v.float = 3.14; // 危险:当前激活的字段是 int

// 标签联合体:自带判别标签,可配合 switch 安全使用
const TaggedValue = union(enum) {
int: i32,
float: f64,
boolean: bool,
};

// 类似 Rust 的枚举:每个变体可携带不同类型的数据
const Message = union(enum) {
text: []const u8,
number: i32,
quit: void,
};

fn handle(msg: Message) void {
switch (msg) {
.text => |t| std.debug.print("text: {s}\n", .{t}),
.number => |n| std.debug.print("number: {}\n", .{n}),
.quit => std.debug.print("quit\n", .{}),
}
}

错误处理

Zig 没有异常,使用错误联合类型(Error Union Type)让错误成为返回值类型的一部分。

定义错误集合

1
2
3
4
5
const FileError = error{
NotFound,
PermissionDenied,
OutOfMemory,
};

错误联合类型:!T

FileError!i32 表示「要么返回 i32,要么返回 FileError 中的错误」:

1
2
3
4
5
6
fn mightFail(condition: bool) FileError!i32 {
if (!condition) {
return FileError.NotFound;
}
return 42;
}

处理错误的几种方式

语法作用
try expr出错则立即把错误返回给调用者
expr catch default出错时使用默认值
expr catch |err| {…}拿到错误值并处理
if (expr) |v| {…} else |err| {…}同时解构成功值与错误
errdefer {…}仅当函数以错误返回时执行清理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// try:向上传播错误
const value = try mightFail(true);

// catch:出错时给默认值
const value_or_default = mightFail(false) catch 0;

// catch:拿到错误值进行处理
const value2 = mightFail(false) catch |err| blk: {
std.debug.print("Got error: {}\n", .{err});
break :blk -1;
};

// if-else:同时解构成功值与错误
if (mightFail(true)) |value| {
std.debug.print("Success: {}\n", .{value});
} else |err| {
std.debug.print("Failed: {}\n", .{err});
}

errdefer 常用于资源清理——成功时不执行,失败时保证释放:

1
2
3
4
5
6
7
fn createResource() !*Resource {
const res = try allocator.create(Resource);
errdefer allocator.destroy(res); // 后续步骤失败时自动清理

try res.init(); // 若这里失败,errdefer 生效
return res;
}

编译时计算(comptime)

Zig 的强大特性之一:任何代码都可以在编译期执行。

comptime 变量与参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 编译期变量
comptime var count: i32 = 0;

// 编译期参数:size 在编译期确定,返回一个全新类型
fn StaticArray(comptime T: type, comptime size: usize) type {
return struct {
data: [size]T,

pub fn get(self: *@This(), idx: usize) T {
return self.data[idx];
}
};
}

const IntArray10 = StaticArray(i32, 10);
var my_array: IntArray10 = undefined;

类型作为参数

类型在 Zig 中是一等值,泛型就是「接收类型、返回代码」:

1
2
3
4
5
6
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}

const m1 = max(i32, 5, 10); // i32 版本
const m2 = max(f64, 3.14, 2.71); // f64 版本

编译期反射

1
2
3
4
5
6
7
8
9
10
11
12
13
fn printStructInfo(comptime T: type) void {
const info = @typeInfo(T);
std.debug.print("Type: {s}\n", .{@typeName(T)});

if (info == .Struct) {
inline for (info.Struct.fields) |field| {
std.debug.print(" Field: {s} ({s})\n", .{
field.name,
@typeName(field.type),
});
}
}
}

泛型编程

Zig 没有专门的泛型语法,用「返回类型的函数」即可实现,下面是一个完整的泛型栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
fn Stack(comptime T: type) type {
return struct {
const Self = @This();

items: []T,
capacity: usize,
len: usize = 0,
allocator: std.mem.Allocator,

pub fn init(allocator: std.mem.Allocator, capacity: usize) !Self {
const items = try allocator.alloc(T, capacity);
return Self{
.items = items,
.capacity = capacity,
.allocator = allocator,
};
}

pub fn deinit(self: *Self) void {
self.allocator.free(self.items);
}

pub fn push(self: *Self, item: T) !void {
if (self.len >= self.capacity) return error.OutOfCapacity;
self.items[self.len] = item;
self.len += 1;
}

pub fn pop(self: *Self) ?T {
if (self.len == 0) return null;
self.len -= 1;
return self.items[self.len];
}
};
}

// 使用:每种类型实例化出独立的具体类型
const IntStack = Stack(i32);
var stack = try IntStack.init(allocator, 10);
defer stack.deinit();

内存管理

Zig 不提供垃圾回收,所有分配都通过显式传入的 分配器(Allocator) 完成。

分配器模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const std = @import("std");

pub fn main() !void {
// 通用分配器(0.14 起更名为 std.heap.DebugAllocator)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // 退出时检测内存泄漏
const allocator = gpa.allocator();

// 动态分配数组
const arr = try allocator.alloc(i32, 10);
defer allocator.free(arr);

for (arr, 0..) |*item, i| {
item.* = @intCast(i);
}
}

常用分配器对比

分配器特点适用场景
GeneralPurposeAllocator通用、线程安全、带泄漏检测日常开发与调试
FixedBufferAllocator在固定缓冲区上分配,无堆分配嵌入式、实时场景
ArenaAllocator只进不出,整体一次释放请求/帧生命周期内的临时分配
page_allocator直接向 OS 申请内存页大块分配、作为底层分配器
1
2
3
4
5
6
7
8
9
10
11
12
// 固定缓冲区分配器
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const fba_allocator = fba.allocator();
const slice = try fba_allocator.alloc(u8, 100);

// Arena 分配器:所有内存随 deinit 一起释放
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const arena_allocator = arena.allocator();
_ = try arena_allocator.alloc(u8, 100);
_ = try arena_allocator.alloc(u8, 200);

与 C 语言互操作

Zig 可以无缝调用 C 代码,也可以被 C 调用。

导入 C 头文件

1
2
3
4
5
6
7
8
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});

pub fn main() void {
_ = c.printf("Hello from C!\n");
}

导出 Zig 函数给 C

1
2
3
export fn zig_add(a: i32, b: i32) i32 {
return a + b;
}

类型映射

1
2
3
4
5
6
7
// Zig 内置与 C 对应的类型
const c_int_val: c_int = 42;
const c_char_val: c_char = 'a';

// C 字符串与 Zig 切片的互转
const c_str: [*c]const u8 = "hello";
const zig_str: []const u8 = std.mem.span(c_str);

构建系统

Zig 自带构建系统,构建脚本本身就是 Zig 代码(build.zig):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// 可执行文件
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);

// zig build run
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

// zig build test
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}

0.14 起 addExecutable 改用 .root_module = b.createModule(.{ ... }) 传入源文件与编译选项。

测试

Zig 内置测试框架,test 块就近写在源码里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const std = @import("std");
const testing = std.testing;

fn add(a: i32, b: i32) i32 {
return a + b;
}

test "basic addition" {
try testing.expect(add(2, 3) == 5);
try testing.expectEqual(@as(i32, 5), add(2, 3));
}

test "expect error" {
const result = mightFail(false);
try testing.expectError(error.NotFound, result);
}

运行测试:

1
zig test file.zig

包管理

Zig 使用 build.zig.zon 声明依赖:

1
2
3
4
5
6
7
8
9
10
.{
.name = "myproject",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.0.tar.gz",
.hash = "1220...",
},
},
}

最佳实践

  1. 错误处理:始终使用错误联合类型而非返回错误码
  2. 内存管理:使用 defer / errdefer 确保资源释放
  3. 编译时计算:利用 comptime 进行泛型和元编程
  4. 显式控制:避免隐式行为,所有操作都应清晰可见
  5. 与 C 互操作:用 Zig 逐步替换 C 代码,而不是一次性重写

总结

Zig 是一门设计精良的系统编程语言,主要特点包括:

  • 显式优于隐式:没有隐藏的控制流或内存分配
  • 编译时代码执行:强大的元编程能力
  • 零成本抽象:高性能的同时保持代码清晰
  • C 语言互操作:无缝集成现有 C 代码
  • 安全第一:通过编译时检查和显式错误处理避免运行时错误

Zig 适合系统编程、嵌入式开发、游戏引擎、编译器等需要高性能和精细控制的场景。

参考资源