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

1. 变量与常量

1.1 变量声明

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

const std = @import("std");

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

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

    // 类型推断
    var z = 50;  // 自动推断为 comptime_int
}

1.2 基本数据类型

Zig 提供了丰富的整数和浮点类型:

// 有符号整数
const a: i8 = -128;      // 8位
const b: i16 = 1000;     // 16位
const c: i32 = 100000;   // 32位
const d: i64 = 1000000;  // 64位

// 无符号整数
const e: u8 = 255;
const f: u32 = 4000000000;

// 浮点数
const g: f32 = 3.14;
const h: f64 = 3.14159265359;

// 布尔类型
const flag: bool = true;

// 字符类型(UTF-8 码点)
const ch: u21 = '中';

1.3 未定义值

Zig 要求显式处理未初始化的变量:

var x: i32 = undefined;  // 显式标记为未定义
x = 10;                   // 使用前必须初始化

2. 数组与切片

2.1 数组

数组是固定大小的同类型元素集合:

// 固定大小数组
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

2.2 切片

切片是对数组的引用,包含指针和长度:

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!";

2.3 多维数组

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

3. 控制流

3.1 条件语句

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", .{});
}

3.2 循环

// while 循环
var i: i32 = 0;
while (i < 5) : (i += 1) {
    std.debug.print("{} ", .{i});
}

// 带 continue 表达式的 while
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;
    }
}

3.3 switch 语句

Zig 的 switch 是详尽的(exhaustive):

const number = 5;

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

// switch 与枚举
const Color = enum { red, green, blue };
const color = Color.red;

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

4. 函数

4.1 函数定义

// 基本函数
fn add(a: i32, b: i32) i32 {
    return a + b;
}

// 无返回值(void)
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),
    };
}

4.2 递归函数

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

4.3 函数指针

const MathOp = fn (a: i32, b: i32) i32;

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

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

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

5. 结构体

5.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 };
const dist = p1.distance(p2);  // 5.0

5.2 默认字段值

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

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

5.3 自引用结构体

const Node = struct {
    value: i32,
    next: ?*Node = null,
};

6. 联合体与枚举

6.1 枚举

const Status = enum {
    pending,
    running,
    completed,
    failed,

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

// 带关联值的枚举(类似 Rust 的枚举)
const Message = union(enum) {
    text: []const u8,
    number: i32,
    quit: void,
};

6.2 联合体

const Value = union {
    int: i32,
    float: f64,
    boolean: bool,
};

var v = Value{ .int = 42 };
// v.float = 3.14;  // 运行时错误:当前激活的字段是 int

// 标签联合体(安全的联合体)
const TaggedValue = union(enum) {
    int: i32,
    float: f64,
    boolean: bool,
};

7. 错误处理

Zig 使用错误联合类型(Error Union Types)进行错误处理:

7.1 定义错误

const FileError = error{
    NotFound,
    PermissionDenied,
    OutOfMemory,
};

7.2 错误联合类型

// 可能返回 i32 或 FileError
fn mightFail(condition: bool) FileError!i32 {
    if (!condition) {
        return FileError.NotFound;
    }
    return 42;
}

// 捕获错误
const result = mightFail(true) catch |err| {
    std.debug.print("Error: {}\n", .{err});
    return;
};

7.3 try 和 catch

// 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;
};

7.4 if-else 处理错误

if (mightFail(true)) |value| {
    std.debug.print("Success: {}\n", .{value});
} else |err| {
    std.debug.print("Failed: {}\n", .{err});
}

8. 可选类型(Optionals)

Zig 使用 ?T 表示类型 T 的可选值:

// 可选类型
const maybe_number: ?i32 = 42;
const no_number: ?i32 = null;

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

// while 解构(常用于迭代器模式)
var iter = RangeIterator{ .current = 0, .end = 5 };
while (iter.next()) |value| {
    std.debug.print("{} ", .{value});
}

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

// .? 操作符(断言非空,不安全)
const definite = maybe_number.?;

9. 编译时计算(comptime)

Zig 的强大特性之一是在编译时执行代码:

9.1 comptime 关键字

// 编译时变量
comptime var count: i32 = 0;

// 编译时函数参数
fn compileTimeFunction(comptime T: type, comptime size: usize) type {
    return struct {
        data: [size]T,

        pub fn get(idx: usize) T {
            return data[idx];
        }
    };
}

// 使用
const IntArray10 = compileTimeFunction(i32, 10);
var my_array: IntArray10 = undefined;

9.2 类型作为参数

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 版本

9.3 编译时反射

const std = @import("std");

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),
            });
        }
    }
}

10. 内存管理

Zig 不提供垃圾回收,需要显式管理内存:

10.1 分配器模式

const std = @import("std");

pub fn main() !void {
    // 获取 GPA(通用分配器)
    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);
    }
}

10.2 固定缓冲区分配器

var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();

// 在小缓冲区上分配,无堆分配
const slice = try allocator.alloc(u8, 100);

10.3 Arena 分配器

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

// 所有内存一起释放
_ = try allocator.alloc(u8, 100);
_ = try allocator.alloc(u8, 200);

11. 指针

11.1 基本指针

var x: i32 = 42;
const ptr: *i32 = &x;  // 单一项指针
ptr.* = 100;            // 解引用

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

11.2 切片指针

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

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

11.3 多指针

// [*]T - 未知长度的多指针(类似 C 指针)
// [*c]T - C 风格的指针

// 示例:C 互操作
extern fn c_function(ptr: [*c]const u8) void;

12. 与 C 语言互操作

Zig 可以无缝与 C 代码集成:

12.1 导入 C 头文件

const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
});

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

12.2 导出 Zig 函数给 C

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

// 使用 C 调用约定
export "C" fn zig_multiply(a: i32, b: i32) i32 {
    return a * b;
}

12.3 类型映射

// C 类型映射
const c_int: c_int = 42;
const c_char: c_char = 'a';
const c_void: c_void = undefined;

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

13. 泛型编程

使用 comptime 实现泛型:

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();

14. 构建系统

Zig 自带构建系统,使用 build.zig

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);

    // 运行命令
    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);

    // 测试
    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);
}

15. 测试

Zig 内置测试框架:

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);
}

// 在文档中测试
/// 计算阶乘
/// ```
/// const fact = factorial(5);
/// try std.testing.expect(fact == 120);
/// ```
fn factorial(n: u32) u32 {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

运行测试:

zig test file.zig

16. 包管理

Zig 使用 build.zig.zon 管理依赖:

.{
    .name = "myproject",
    .version = "0.1.0",
    .dependencies = .{
        .zap = .{
            .url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.0.tar.gz",
            .hash = "1220...",
        },
    },
}

17. 最佳实践

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

18. 总结

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

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

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

参考资源