Zig 语言基础与语法特色

Zig 是一门专注于系统级编程的现代语言,目标是成为 C 的更好替代品。本教程深入讲解 Zig 的变量声明、数据类型、控制流、函数以及编译时反射机制。

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 是一个值得深入学习的语言。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「编程语言」更多文章

  1. Zig 错误处理与控制流设计