1. Zig 语言简介
Zig 是一门由 Andrew Kelley 创建的通用系统级编程语言,首次发布于 2016 年。其设计哲学可以概括为:“专注于调试应用而非调试编程语言知识”。与 Rust 的激进安全策略不同,Zig 选择了一条更贴近 C 的渐进式路线,在保持底层控制力的同时提供现代化的语言特性。
Zig 的核心目标包括:
- 成为 C 的更好替代品:能够直接导入 C 头文件,无需 FFI 绑定
- 无隐藏控制流:没有默认构造函数、运算符重载或隐式类型转换
- 编译时代码执行:几乎任何代码都可以在编译时运行
- 显式内存管理:提供安全抽象但不强制垃圾回收
- 完善的交叉编译支持:内置工具链可直接编译到任意目标平台
2. 安装与环境准备
# macOS/Linux (使用官方安装脚本)
curl -L https://ziglang.org/download/0.13.0/zig-macos-aarch64-0.13.0.tar.xz | tar -xJ
# 验证安装
zig version # 输出: 0.13.0
zig zen # Zig 设计哲学
# 初始化新项目
mkdir myzig && cd myzig
zig init-exe # 可执行项目
zig init-lib # 库项目
每次编译时,Zig 会自动下载目标平台的 libc 头文件,这意味着交叉编译无需额外工具链。
3. 基本语法结构
3.1 变量声明与常量
Zig 使用 var 声明可变变量,const 声明常量(编译时已知值)。
const std = @import("std");
pub fn main() void {
// var: 可修改变量
var count: u32 = 0;
count += 1;
// const: 不可修改的绑定
const pi: f64 = 3.14159;
// pi = 3.14; // 编译错误!
// var 必须显式初始化
var x: i32 = undefined; // 未初始化标记
x = 42;
// 类型推断(仅限 const)
const inferred = @as(i32, 100);
}
与 Go 不同,Zig 不允许变量未初始化就被使用——undefined 是一个明确的标记值,编译器会在安全模式下拒绝传递它。
3.2 基础数据类型
// 整数类型
const a: i8 = -127; // 有符号 8 位
const b: u32 = 4000000; // 无符号 32 位
const c: i64 = -999999999999;
const d: usize = 100; // 平台相关大小(通常 64 位)
const e: u8 = 0b1010; // 二进制字面量
// 浮点数
const f: f32 = 1.5;
const g: f64 = 2.718281828459045;
// 布尔值
const flag: bool = true;
const neg: bool = !flag;
// 可选类型(可能为 null)
const maybe: ?i32 = null;
const definitely: ?i32 = 42;
// 错误联合类型
const result: !i32 = 42; // error!i32 的简写
4. 控制流与循环
4.1 条件语句
// if-else 表达式(必须覆盖所有分支)
const age: u8 = 25;
const category = if (age < 13) "child"
else if (age < 20) "teenager"
else "adult";
// switch 支持范围与多值匹配
const grade: u8 = 85;
switch (grade) {
90...100 => std.debug.print("A\n", .{}),
80...89 => std.debug.print("B\n", .{}),
70...79 => std.debug.print("C\n", .{}),
60...69 => std.debug.print("D\n", .{}),
else => std.debug.print("F\n", .{}),
}
注意 Zig 的 if 是一个表达式,它会返回所选择分支的值。
4.2 循环结构
// while 循环
var i: u32 = 0;
while (i < 5) : (i += 1) {
std.debug.print("i = {d}\n", .{i});
}
// for 迭代数组/切片
const items = [_]i32{ 10, 20, 30, 40 };
for (items, 0..) |value, idx| {
std.debug.print("[{d}] = {d}\n", .{ idx, value });
}
// 带 continue expression 的 while
var n: u32 = 1;
while (n < 100) : (n *= 2) {
std.debug.print("{d}\n", .{n});
}
// 无限循环(显式 break)
while (true) {
// ...
break;
}
循环的 continue expression 语法让迭代器更新逻辑一目了然,避免了 C 语言中常见的 for (;;) 混乱结构。
5. 函数与参数传递
5.1 函数定义
// 基本函数
fn add(x: i32, y: i32) i32 {
return x + y;
}
// 多返回值(通过结构体实现)
fn divide(dividend: i32, divisor: i32) struct { quotient: i32, remainder: i32 } {
return .{
.quotient = @divTrunc(dividend, divisor),
.remainder = @mod(dividend, divisor),
};
}
// 错误返回
fn safe_divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
5.2 指针传递策略
fn swap(a: *i32, b: *i32) void {
const tmp = a.*;
a.* = b.*;
b.* = tmp;
}
// 数组切片传递
fn sum_array(items: []const i32) i32 {
var total: i32 = 0;
for (items) |item| total += item;
return total;
}
Zig 的函数签名清晰地标明了参数是通过值传递还是指针传递,以及数组是否需要可变访问。
6. 结构体与枚举
6.1 结构体定义
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 };
std.debug.print("距离: {d:.2}\n", .{p1.distance(p2)});
6.2 枚举与联合类型
const Color = enum {
Red,
Green,
Blue,
};
const Shape = union(enum) {
Circle: struct { radius: f32 },
Rectangle: struct { width: f32, height: f32 },
Triangle: struct { base: f32, height: f32 },
pub fn area(self: Shape) f32 {
return switch (self) {
.Circle => |c| std.math.pi * c.radius * c.radius,
.Rectangle => |r| r.width * r.height,
.Triangle => |t| 0.5 * t.base * t.height,
};
}
};
union(enum) 是 Zig 中的标记联合(tagged union),在编译时即可保证模式匹配的完备性。
7. 编译时计算(comptime)
这是 Zig 最强大的特性之一。comptime 关键字告诉编译器:这段代码在编译时执行。
fn make_array(comptime T: type, comptime size: usize) [size]T {
var arr: [size]T = undefined;
var i: usize = 0;
while (i < size) : (i += 1) {
arr[i] = @as(T, i);
}
return arr;
}
const squares = make_array(u32, 10);
// squares 的类型是 [10]u32,所有值在编译时已确定
// 泛型函数
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
const m1 = max(i32, 10, 20); // 返回 i32
const m2 = max(f64, 3.14, 2.71); // 返回 f64
编译时计算让 Zig 能够在不引入复杂类型系统的情况下提供泛型编程能力。
8. 常用标准库组件
const std = @import("std");
// ArrayList: 动态数组
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
try list.append(10);
try list.append(20);
try list.append(30);
// HashMap
var map = std.StringHashMap(i32).init(allocator);
defer map.deinit();
try map.put("one", 1);
try map.put("two", 2);
const val = map.get("one").?;
// 格式化输出
std.debug.print("Hello, {s}! 数值: {d}\n", .{"Zig", 42});
defer 语句确保分配的资源在作用域结束时自动释放,这是 Zig 管理临时资源的标准模式。
9. 与 C 的直接集成
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
pub fn main() void {
const msg = "Hello from Zig\n";
_ = c.printf("%s", msg);
}
Zig 内置了 C 翻译器,可以直接解析 C 头文件,无需手动编写 FFI 绑定。
10. 总结
Zig 语言在保持系统级控制能力的同时,通过以下设计显著提升了开发体验:
- 显式优于隐式:没有隐藏的内存分配、没有隐式类型转换
- 编译时即力量:
comptime提供了零成本抽象的能力 - 安全第一可选:安全检查和调试模式可以按需启用
- C 无缝互操作:直接导入 C 头文件,零开销调用 C 函数
- 现代构建系统:内置的包管理和交叉编译支持
对于熟悉 C 语言但需要更现代工具链的系统开发者,Zig 是一个值得深入学习的语言。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。