Zig 教程

Zig 教程

Zig 是一门新兴的系统编程语言,由 Andrew Kelley 于 2015 年开始开发,目标是成为 C 语言的精神继承者:简单、可预测、无隐藏控制流,同时提供内存安全与强大的编译期能力。

Zig 的特点

  • 无 GC 无运行时:直接编译为机器码,性能与 C 相当
  • 内存安全:编译期检查悬垂指针、未定义行为,可显式管理内存
  • 编译期计算:comptime 强大的元编程能力
  • 无隐藏控制流:错误处理显式,不依赖异常
  • 可交叉编译:内置交叉编译支持,无需工具链
  • C 兼容:可直接包含 C 头文件,替代 C 的构建系统

Hello World

const std = @import("std");

pub fn main() !void {
    std.debug.print("Hello, Zig!\n", .{});
}

学习路线

  1. 环境安装与基本语法
  2. 注释、数据类型、变量常量
  3. 循环、流程控制、运算符
  4. 函数、数组切片
  5. 结构体枚举、错误处理、内存管理

Zig 环境安装

Zig 环境安装

1. 安装方式

# macOS (Homebrew)
brew install zig

# Ubuntu / Debian
sudo snap install zig --classic

# 源码包
# 从 https://ziglang.org/download 下载 tar.xz
tar -xf zig-linux-x86_64-0.13.0.tar.xz
sudo mv zig-linux-x86_64-0.13.0 /opt/zig
export PATH=$PATH:/opt/zig

# Windows
# 下载 zip 解压,加入 PATH

2. 验证安装

zig version
# 0.13.0

zig help
# 显示命令帮助

3. 项目初始化

# 创建新项目
mkdir hello-zig && cd hello-zig
zig init

# 生成的文件结构
# build.zig       构建脚本
# build.zig.zon   依赖清单
# src/
#   main.zig      主程序
#   root.zig      库入口

# 编译运行
zig build run

4. 常用命令

zig build                 # 构建项目
zig build run             # 构建并运行
zig test src/main.zig     # 运行测试
zig fmt src/main.zig      # 格式化代码
zig run hello.zig         # 直接编译运行单文件
zig build-exe hello.zig   # 编译为可执行文件
zig cc file.c             # 作为 C 编译器使用

5. IDE 支持

# VS Code 扩展
# "Zig Language" (ziglang.vscode-zig)

# 语言服务器
zig build-exe -femit-bin=zls ... # 或安装 zls
# brew install zls

Zig 基本语法

Zig 基本语法

1. Hello World 详解

// 导入标准库
const std = @import("std");

// pub 公开,main 是入口,!void 表示可能出错
pub fn main() !void {
    // 格式化打印:.{} 是格式化参数占位
    std.debug.print("Hello, Zig!\n", .{});
}

2. 语句与分号

// 表达式语句需要分号
const x = 5;
var y: i32 = 10;
y += 1;

// 块(block)可以返回值
const z = blk: {
    const a = 1;
    const b = 2;
    break :blk a + b;   // 块标签返回值
};  // z = 3

3. 变量声明

// const 不可变(推荐)
const name = "Zig";
const count: u32 = 100;

// var 可变
var score: i32 = 0;
score = 90;

// 常量表达式
const MAX = comptime 100;

// 导出符号
export fn add(a: i32, b: i32) i32 { return a + b; }

4. 导入

// 导入标准库模块
const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;

// 导入本地文件
const utils = @import("utils.zig");

// 导入包(依赖)
// build.zig.zon 中声明依赖后
const dep = @import("dep-name");

5. 空值 null 与 optional

// 可选类型(用 ? 前缀)
var maybe: ?i32 = null;
maybe = 5;

// 解包:捕获 else
if (maybe) |value| {
    print("{d}\n", .{value});
} else {
    print("无值\n", .{});
}

// 强制解包(危险,仅在确定非空时用)
const v = maybe.?;

Zig 注释

Zig 注释

1. 三种注释

// 普通注释

/// 文档注释:用于 API 文档,可被 zig 生成文档
/// 支持 markdown 语法
const Foo = struct {
    /// 字段说明
    x: i32,
};

//! 顶层文档注释:描述整个文件
//! 通常放在文件开头

// TODO: 待办标记
// FIXME: 需要修复

2. 文档生成

// 生成 HTML 文档
zig build-exe lib.zig -femit-docs

// 或 zig build docs(项目配置好时)

// 文档注释示例
/// 计算两个数的和
///
/// 参数:
/// - a: 第一个数
/// - b: 第二个数
///
/// 返回两数之和
pub fn sum(a: i32, b: i32) i32 {
    return a + b;
}

3. 注释风格建议

  • 用 /// 注释公开 API
  • 用 //! 注释文件用途
  • 普通 // 解释代码逻辑
  • 遵循 zig fmt 的注释格式

Zig 数据类型

Zig 数据类型

1. 整数类型

// 有符号: i8 i16 i32 i64 i128
const a: i32 = -100;

// 无符号: u8 u16 u32 u64 u128
const b: u8 = 255;

// 任意位宽整数
const c: u7 = 100;   // 7 位无符号

// usize 指针宽度(平台相关)
const n: usize = 100;

// 强制类型转换
const x: i32 = @intCast(b);   // u8 → i32
const y: u8 = @intCast(a);    // 需要安全转换
const z: u8 = @truncate(300); // 截断:44

2. 浮点类型

const f1: f32 = 3.14;
const f2: f64 = 3.14159265358979;
const f3: f16 = 1.5;

// 特殊值
const inf = std.math.inf(f64);
const nan = std.math.nan(f64);

// 转换
const i: i32 = @intFromFloat(3.9);   // 3(截断)
const f: f64 = @floatFromInt(100);   // 100.0

3. 布尔与字符

const ok: bool = true;
const no: bool = false;

// 逻辑运算
const r1 = true and false;   // false
const r2 = true or false;    // true
const r3 = !true;            // false

// 字符:u21 存储 Unicode 码点
const ch: u21 = 'A';
const chinese: u21 = '中';

// 字符串字面量
const s: []const u8 = "hello";
const cstr: [:0]const u8 = "c风格";

4. 数组与切片

// 数组(定长)
const arr: [5]i32 = [_]i32{ 1, 2, 3, 4, 5 };
const len = arr.len;          // 5
const first = arr[0];

// 切片(动态长度视图)
const slice: []const i32 = &arr;
const sub = arr[1..3];        // {2, 3}

// 字符串是字节切片
const s = "hello";
const byte_count = s.len;     // 5

5. 复合类型

// 结构体
const Point = struct { x: f64, y: f64 };
const p = Point{ .x = 1.0, .y = 2.0 };

// 枚举
const Color = enum { red, green, blue };
const c = Color.red;

// 联合(标签联合)
const Value = union(enum) {
    int: i32,
    float: f64,
    str: []const u8,
};

// 可选
var opt: ?i32 = null;

// 错误联合
fn f() error{OutOfMemory}!void {}

Zig 变量和常量

Zig 变量和常量

1. const 常量

// const:不可变,编译期已知(多数情况下)
const pi = 3.14159;
const name: []const u8 = "Zig";
const arr = [_]i32{ 1, 2, 3 };

// comptime 强制编译期计算
const table = comptime blk: {
    var t: [10]i32 = undefined;
    for (0..10) |i| {
        t[i] = @intCast(i * i);
    }
    break :blk t;
};

2. var 变量

// var:可变变量
var count: i32 = 0;
count += 1;

// 需要初始化或明确 undefined
var buffer: [256]u8 = undefined;   // 未初始化(危险,慎用)

// 变量遮蔽
const x = 1;
{
    const x = 2;   // 内层遮蔽外层
    print("{d}\n", .{x});  // 2
}
print("{d}\n", .{x});      // 1

3. 类型推导

// 自动推断类型
const a = 42;          // comptime_int
const b: i32 = 42;
const c = 3.14;        // comptime_float
const d: f64 = 3.14;

// 数组类型推导
const arr = [_]u8{ 1, 2, 3 };   // [3]u8

// 结构体类型
const p = .{ .x = 1, .y = 2 };  // 匿名结构体

4. 顶层与局部作用域

// 顶层(文件级)
const TOP = 100;

fn myFunc() void {
    const local = 10;   // 函数内局部
    {
        const inner = 20;  // 块内
    }
    // inner 在这里不可见
}

// 容器级常量
const Config = struct {
    const default_port: u16 = 8080;
};

5. 变量安全建议

  • 默认使用 const,确需修改才用 var
  • 避免 undefined 未初始化(易出未定义行为)
  • 整数运算注意溢出(可开启溢出检查)
  • 使用 @intCast 显式类型转换

Zig 循环

Zig 循环

1. while 循环

const std = @import("std");

// 基本 while
var i: i32 = 0;
while (i < 5) : (i += 1) {
    std.debug.print("{d}\n", .{i});
}

// 无 continue 表达式的写法
var j: i32 = 0;
while (j < 3) {
    std.debug.print("j={d}\n", .{j});
    j += 1;
}

// while 可选
while (maybe) |value| {
    // value 已解包
} else {
    // 无值
}

2. for 循环

// 遍历数组
const arr = [_]i32{ 10, 20, 30 };
for (arr) |value| {
    std.debug.print("{d}\n", .{value});
}

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

// 遍历切片
const slice: []const i32 = &arr;
for (slice[1..]) |v| { ... }

// 多个数组并行
const a = [_]i32{ 1, 2, 3 };
const b = [_]i32{ 4, 5, 6 };
for (a, b) |x, y| {
    std.debug.print("{d}+{d}={d}\n", .{ x, y, x + y });
}

3. break 与 continue

var n: i32 = 0;
while (n < 10) : (n += 1) {
    if (n == 3) continue;   // 跳过 3
    if (n == 7) break;      // 到 7 退出
    std.debug.print("{d}\n", .{n});
}

// 标签跳出多层循环
outer: while (true) {
    inner: while (true) {
        break :outer;       // 跳出外层
    }
}

4. 循环返回值

// while 可以带 else 返回值
const found = blk: {
    var i: usize = 0;
    while (i < arr.len) : (i += 1) {
        if (arr[i] == target) break :blk i;
    }
    break :blk null;   // 未找到
};

5. 循环性能与边界

// 边界检查:Zig 默认检查数组越界
// const v = arr[10];  // 编译/运行时报错

// 关闭安全检查(不推荐,除非确定安全)
// const v = arr[10] catch unreachable;

// 无限循环
while (true) {
    // 常用于事件循环
}

Zig 流程控制

Zig 流程控制

1. if 表达式

const std = @import("std");

const x: i32 = 10;

// 基本 if
if (x > 0) {
    std.debug.print("正数\n", .{});
} else if (x < 0) {
    std.debug.print("负数\n", .{});
} else {
    std.debug.print("零\n", .{});
}

// if 是表达式,可赋值
const label = if (x > 0) "positive" else "non-positive";

// 可选值解包
var maybe: ?i32 = 5;
if (maybe) |v| {
    std.debug.print("值: {d}\n", .{v});
} else {
    std.debug.print("无值\n", .{});
}

2. switch 表达式

const Color = enum { red, green, blue };

const c = Color.green;
const rgb = switch (c) {
    .red => 0xFF0000,
    .green => 0x00FF00,
    .blue => 0x0000FF,
};

// 数字 switch
const n: u8 = 3;
const word = switch (n) {
    0 => "zero",
    1 => "one",
    2, 3, 4 => "small",     // 多值合并
    else => "big",           // 默认
};

// 区间匹配
const score: i32 = 85;
const grade = switch (score) {
    0...59 => "D",
    60...79 => "C",
    80...89 => "B",
    90...100 => "A",
    else => unreachable,
};

3. switch 穷尽性

// 枚举 switch 必须覆盖所有分支(否则编译错误)
const Direction = enum { north, south, east, west };

fn describe(d: Direction) []const u8 {
    return switch (d) {
        .north => "北",
        .south => "南",
        .east => "东",
        .west => "西",
    };   // 少一个分支会编译失败!
}

4. 错误处理控制流

// if 捕获错误
if (mayFail()) |value| {
    // 成功分支
} else |err| {
    // 错误分支
    std.debug.print("错误: {}\n", .{err});
}

// switch 处理错误
const result = mayFail() catch {
    std.debug.print("出错,回退\n", .{});
    return;
};

5. 控制流最佳实践

  • 优先使用表达式形式(if/switch 返回值)
  • 枚举 switch 保持穷尽
  • 可选值用 if 解包而非 .?(强制)
  • 用 catch 或 errdefer 显式处理错误

Zig 运算符

Zig 运算符

1. 算术运算符

const a: i32 = 7;
const b: i32 = 3;

const sum = a + b;      // 10
const diff = a - b;     // 4
const prod = a * b;     // 21
const quot = a / b;     // 2(整数除法截断)
const rem = a % b;      // 1(取余)

// 浮点运算
const x: f64 = 7.0;
const y: f64 = 3.0;
const q = x / y;        // 2.333...

// 复合赋值
var n: i32 = 5;
n += 3;    // 8
n -= 2;    // 6
n *= 4;    // 24
n /= 6;    // 4
n %= 3;    // 1

// 幂与根(标准库)
const pow = std.math.pow(f64, 2.0, 10.0);  // 1024.0
const sqrt = std.math.sqrt(16.0);          // 4.0
const abs = std.math.absInt(-5) catch 0;   // 5

2. 位运算符

const m: u8 = 0b1100;
const n: u8 = 0b1010;

const and = m & n;        // 0b1000
const or = m | n;         // 0b1110
const xor = m ^ n;        // 0b0110
const not = ~m;           // 取反
const shl = m << 1;       // 0b11000(左移)
const shr = m >> 1;       // 0b0110(右移)

3. 比较运算符

const a = 5;
const b = 10;

a == b    // false
a != b    // true
a < b     // true
a <= b    // true
a > b     // false
a >= b    // false

// 浮点比较(注意 NaN)
const f = std.math.nan(f64);
f == f    // false(NaN 不等于自身)
// 用 std.math.isNan(f) 判断

4. 逻辑运算符

// and / or / not
const ok = true;
const ready = false;

ok and ready   // false
ok or ready    // true
!ok            // false

// 短路求值
// 与 C 不同,Zig 的 and/or 总是求值两侧?
// 不,zig 中 and/or 不短路!注意:

// 需要短路时用 if/while 嵌套:
if (a != null and a.? > 0) ...  // 仍会求值 a.?

// 安全写法
if (a) |v| {
    if (v > 0) { ... }
}

5. 类型相关运算符

// 取地址
var x: i32 = 5;
const ptr: *i32 = &x;
ptr.* = 10;              // 解引用修改

// 数组元素地址
const arr = [_]i32{ 1, 2, 3 };
const p = &arr[1];

// 类型转换
const i: i32 = @intCast(100);
const f: f64 = @floatFromInt(100);
const u: u8 = @truncate(300);   // 44(截断)

// 大小
const size = @sizeOf(i32);      // 4
const align = @alignOf(i32);    // 4

Zig 函数

Zig 函数

1. 函数定义

const std = @import("std");

// 无返回值
fn greet(name: []const u8) void {
    std.debug.print("Hello, {s}!\n", .{name});
}

// 带返回值
fn add(a: i32, b: i32) i32 {
    return a + b;
}

// 单表达式函数
fn square(x: i32) i32 {
    return x * x;
}

// 调用
pub fn main() void {
    greet("Zig");
    const s = add(2, 3);
    std.debug.print("和: {d}\n", .{s});
}

2. 参数与返回类型

// 切片参数(推荐,不复制数据)
fn sum(items: []const i32) i32 {
    var total: i32 = 0;
    for (items) |v| total += v;
    return total;
}

// 返回多个值(元组/结构体)
fn divmod(a: i32, b: i32) struct { quot: i32, rem: i32 } {
    return .{ .quot = a / b, .rem = a % b };
}

// 可选返回
fn find(arr: []const i32, target: i32) ?usize {
    for (arr, 0..) |v, i| {
        if (v == target) return i;
    }
    return null;
}

// 错误返回
fn parseNumber(s: []const u8) !i32 {
    return std.fmt.parseInt(i32, s, 10);
}

3. 错误联合与 catch

fn risky() !i32 {
    if (true) return error.Failed;
    return 42;
}

pub fn main() void {
    // catch 提供回退值
    const v1 = risky() catch 0;
    std.debug.print("{d}\n", .{v1});  // 0

    // 传播错误
    fn wrapper() !void {
        const v = try risky();   // 成功则继续,失败则返回错误
        std.debug.print("{d}\n", .{v});
    }

    // 处理错误
    if (risky()) |value| {
        std.debug.print("成功: {d}\n", .{value});
    } else |err| {
        std.debug.print("错误: {}\n", .{err});
    }
}

4. 泛型函数

// 用 anytype 或 comptime 类型参数
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

// 调用时自动实例化
const m1 = max(i32, 3, 5);
const m2 = max(f64, 1.5, 2.5);

// 对任意类型集合求和
fn sumAll(comptime T: type, items: []const T) T {
    var total: T = 0;
    for (items) |v| total += v;
    return total;
}

5. 内联函数与闭包

// inline 内联
inline fn fastMul(a: i32, b: i32) i32 {
    return a * b;
}

// 函数指针
const handler = fn (i32) void;
fn process(h: handler, value: i32) void {
    h(value);
}

// 编译期执行
fn tableAt(comptime i: usize) i32 {
    return @intCast(i * i);
}
const value = comptime tableAt(5);   // 25

Zig 数组和切片

Zig 数组和切片

1. 数组(Array)

const std = @import("std");

// 定长数组
const a: [5]i32 = [_]i32{ 1, 2, 3, 4, 5 };
const b = [3]u8{ 10, 20, 30 };

// 数组属性
const len = a.len;        // 5
const first = a[0];       // 1
const last = a[a.len - 1];

// 修改元素(需 var)
var c = [_]i32{ 0, 0, 0 };
c[1] = 100;

// 默认值数组
var zeros = [_]i32{0} ** 10;   // 10 个 0
// {0} ** 10 表示重复 10 次

2. 数组遍历与操作

// 遍历
for (a) |v| {
    std.debug.print("{d} ", .{v});
}

// 切片视图
const slice = a[1..3];      // {2, 3}

// 复制
var dst: [5]i32 = undefined;
@memcpy(&dst, &a);

// 比较
const eq = std.mem.eql(i32, &a, &a);

// 排序(标准库)
var arr = [_]i32{ 3, 1, 2 };
std.mem.sort(i32, &arr, {}, std.sort.asc(i32));

3. 切片(Slice)

// 切片是对数组的视图,包含指针和长度
const s: []const i32 = &a;
const len = s.len;      // 5
const item = s[0];

// 从数组创建切片
const s2 = a[0..3];     // 前 3 个元素
const s3 = a[2..];      // 从索引 2 到结尾
const all = a[0..];     // 整个数组

// 可写切片
var arr2 = [_]i32{ 1, 2, 3 };
const ws: []i32 = &arr2;
ws[0] = 99;             // 修改原数组

4. 字符串处理

// 字符串字面量是 []const u8
const hello: []const u8 = "hello";
const len = hello.len;      // 5(字节数)

// 拼接
var buf: [32]u8 = undefined;
const s = std.fmt.bufPrint(&buf, "{s}-{d}", .{ "id", 42 }) catch "";
// s = "id-42"

// 分割
var it = std.mem.splitSequence(u8, "a,b,c", ",");
while (it.next()) |part| {
    std.debug.print("{s}\n", .{part});
}

// 转数字
const n = std.fmt.parseInt(i32, "42", 10) catch 0;

5. 动态数组(ArrayList)

// 堆分配的动态数组
var list = std.ArrayList(i32).init(std.heap.page_allocator);
defer list.deinit();     // 释放

try list.append(1);
try list.append(2);
try list.appendSlice(&[_]i32{ 3, 4 });

for (list.items) |v| {
    std.debug.print("{d} ", .{v});
}

list.clearRetainingCapacity();   // 清空

Zig 结构体和枚举

Zig 结构体和枚举

1. 结构体定义

const std = @import("std");

const Point = struct {
    x: f64,
    y: f64,

    // 方法(第一个参数 self)
    fn distance(self: Point, other: Point) f64 {
        const dx = self.x - other.x;
        const dy = self.y - other.y;
        return std.math.sqrt(dx * dx + dy * dy);
    }

    // 静态方法
    fn origin() Point {
        return .{ .x = 0, .y = 0 };
    }
};

pub fn main() void {
    const p1 = Point{ .x = 0, .y = 0 };
    const p2 = Point{ .x = 3, .y = 4 };
    std.debug.print("距离: {d}\n", .{p1.distance(p2)});

    const o = Point.origin();
    std.debug.print("原点: ({d},{d})\n", .{ o.x, o.y });
}

2. 默认值与可选字段

const Config = struct {
    host: []const u8 = "localhost",
    port: u16 = 8080,
    debug: bool = false,
};

// 只覆盖需要的字段
const dev = Config{ .port = 9090, .debug = true };
const prod = Config{};    // 全默认

// 可选字段
const Opt = struct {
    name: ?[]const u8 = null,
};

3. 枚举定义

const Color = enum {
    red,
    green,
    blue,

    // 枚举方法
    fn hex(self: Color) u32 {
        return switch (self) {
            .red => 0xFF0000,
            .green => 0x00FF00,
            .blue => 0x0000FF,
        };
    }

    // 从整数创建
    fn fromCode(code: u8) ?Color {
        return std.meta.intToEnum(Color, code) catch null;
    }
};

// 使用
const c = Color.red;
std.debug.print("{X}\n", .{c.hex()});   // FF0000

// 遍历枚举
inline for (@typeInfo(Color).@"enum".fields) |field| {
    std.debug.print("{s}\n", .{field.name});
}

4. 带值的枚举

const HttpStatus = enum(u16) {
    ok = 200,
    not_found = 404,
    error = 500,

    fn isOk(self: HttpStatus) bool {
        return self == .ok;
    }
};

const code: u16 = @intFromEnum(HttpStatus.not_found);  // 404
const status: HttpStatus = @enumFromInt(404);

5. 联合体与标签联合

// 简单联合
const U = union {
    int: i32,
    float: f64,
};

// 标签联合(推荐,安全)
const Value = union(enum) {
    int: i32,
    float: f64,
    text: []const u8,
    none,

    fn describe(self: Value) void {
        switch (self) {
            .int => |v| std.debug.print("整数: {d}\n", .{v}),
            .float => |v| std.debug.print("浮点: {d}\n", .{v}),
            .text => |s| std.debug.print("文本: {s}\n", .{s}),
            .none => std.debug.print("空\n", .{}),
        }
    }
};

const v = Value{ .int = 42 };
v.describe();

zig 错误处理

Zig 错误处理

Zig 不使用异常,错误通过返回值显式传播,清晰可控、零开销。

1. 错误集与错误联合

const std = @import("std");

// 错误集定义
const MyError = error{
    OutOfMemory,
    InvalidInput,
    NotFound,
};

// 错误联合类型:成功返回 i32,失败返回错误
fn divide(a: i32, b: i32) MyError!i32 {
    if (b == 0) return error.InvalidInput;
    return a / b;
}

2. try 与 catch

// try:失败则提前返回错误
fn caller() !void {
    const result = try divide(10, 2);
    std.debug.print("{d}\n", .{result});
}

// catch:提供回退值
const v1 = divide(10, 0) catch 0;
std.debug.print("{d}\n", .{v1});   // 0

// catch 块
const v2 = divide(10, 0) catch blk: {
    std.debug.print("计算失败,回退\n", .{});
    break :blk -1;
};

// 忽略错误(谨慎)
_ = divide(1, 0) catch {};

3. if/switch 处理错误

// if 捕获
if (divide(10, 0)) |value| {
    std.debug.print("成功: {d}\n", .{value});
} else |err| {
    std.debug.print("错误: {}\n", .{err});
}

// switch 按错误类型分支
switch (divide(10, 0)) {
    error.InvalidInput => std.debug.print("输入错误\n", .{}),
    else => |e| std.debug.print("其他错误: {}\n", .{e}),
}

4. errdefer 与 defer

// defer:函数退出时执行(无论成败)
fn readFile() ![]u8 {
    const file = try std.fs.cwd().openFile("data.txt", .{});
    defer file.close();    // 退出时关闭

    // errdefer:仅出错时执行
    const buf = try file.readToEndAlloc(
        std.heap.page_allocator,
        1024 * 1024
    );
    errdefer std.heap.page_allocator.free(buf);  // 出错释放

    return buf;
}

5. 错误处理模式

// 捕获特定错误
fn safeDivide(a: i32, b: i32) i32 {
    return divide(a, b) catch |err| switch (err) {
        error.InvalidInput => 0,
        else => unreachable,  // 其他错误不可能
    };
}

// 错误合并(不同错误集)
fn multi() error{A}!void {}
fn multi2() error{B}!void {}

fn combined() error{A || B}!void {
    try multi();
    try multi2();
}

// 全局分配器错误处理
fn alloc() ![]u8 {
    const buf = try std.heap.page_allocator.alloc(u8, 100);
    defer std.heap.page_allocator.free(buf);
    return buf;
}

6. 错误处理最佳实践

  • 错误信息具体:error.InvalidInput 优于 error.Failed
  • 用 try 传播、catch 兜底、switch 分类处理
  • 用 errdefer 做资源清理
  • 避免吞掉错误(空 catch)

Zig 内存管理

Zig 内存管理

Zig 没有 GC 也没有自动内存管理,内存分配与释放全部显式,配合编译期检查保证安全。

1. 分配器概念

const std = @import("std");

// 常用分配器
std.heap.page_allocator        // 直接调用操作系统,慢但简单
std.heap.c_allocator           // 包装 C 的 malloc/free
std.heap.GeneralPurposeAllocator(.{})  // 通用分配器(带检测)
std.heap.ArenaAllocator         // 竞技场:一次性释放

// 推荐:开发时用带检测的分配器
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();   // 检测内存泄漏,泄漏会报错

2. 分配与释放

// 分配单个值
const p = try allocator.create(i32);
p.* = 42;
allocator.destroy(p);    // 释放

// 分配数组
const arr = try allocator.alloc(i32, 100);
defer allocator.free(arr);   // 释放

// 重新分配(扩容)
var buf = try allocator.alloc(u8, 100);
buf = try allocator.realloc(buf, 200);
defer allocator.free(buf);

// 分配并填充
const str = try allocator.dupe(u8, "hello");
defer allocator.free(str);

3. 内存安全原则

// 原则:谁分配,谁释放
// 使用 defer 保证释放

fn process() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const a = gpa.allocator();

    const buf = try a.alloc(u8, 1024);
    defer a.free(buf);    // 函数退出自动释放

    // 使用 buf ...
}

4. 悬垂指针检测

// 释放后使用会被编译器/分配器检测
const p = try allocator.create(i32);
p.* = 10;
allocator.destroy(p);
// p.* = 20;  // 错误!已释放

// 用可选指针表示可能失效
var ptr: ?*i32 = try allocator.create(i32);
ptr.?.* = 10;
allocator.destroy(ptr.?);
ptr = null;   // 标记失效

5. ArenaAllocator 场景

// 大量短期分配,结束时一次性释放
fn handleRequest() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();    // 一次性释放所有

    const a = arena.allocator();
    // 任意多次分配,不用单独 free
    const s1 = try a.dupe(u8, "request data");
    const s2 = try a.dupe(u8, "more data");
    // ...
}

6. 内存泄漏检测

// 用 GeneralPurposeAllocator 自动检测
test "memory safety" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();   // 若有泄漏,这里报错

    const a = gpa.allocator();
    const buf = try a.alloc(u8, 100);
    // 忘记 free → 测试失败并报告泄漏
    _ = buf;
}

// 运行测试
// zig test src/main.zig
第 1 / 14 节