Zig 语法指南

面向 Go 开发者的 Zig 语言核心语法详解

基础语法

Hello World 对比

Zig

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, World!\n", .{});
}

Go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

关键差异

  • Zig 使用 const std = @import("std") 导入标准库
  • Zig 的 main 函数可以返回错误类型 !void
  • Zig 使用 try 处理可能错误的操作
  • Go 使用简单的 import "fmt"
  • Go 的 main 函数无返回值

变量与常量

变量声明

Zig

// 可变变量
var x: i32 = 10;
x = 20;

// 不可变变量
const y: i32 = 30;
// y = 40; // 编译错误

// 类型推断
var name = "Zig";
const version = 0.15;

Go

// 可变变量
var x int = 10
x = 20

// 不可变变量
const y int = 30
// y = 40 // 编译错误

// 类型推断
name := "Go"
const version = 1.21
特性 Zig Go
可变变量 var x: i32 = 10; var x int = 10
不可变变量 const x: i32 = 10; const x int = 10
类型推断 var x = 10; x := 10
全局变量 var x: i32 = 10; var x int = 10

数据类型

基本类型对比

类型类别 Zig Go 说明
有符号整数 i8, i16, i32, i64, i128 int, int8, int16, int32, int64 Zig 明确指定位数
无符号整数 u8, u16, u32, u64, u128 uint, uint8, uint16, uint32, uint64 Zig 支持 128 位
浮点数 f16, f32, f64, f128 float32, float64 Zig 支持更多精度
布尔值 bool bool 相同
字符串 []const u8 string Zig 更底层

数组和切片

Zig

// 固定大小数组
const arr: [5]i32 = [1, 2, 3, 4, 5];

// 切片(指针 + 长度)
var slice: []i32 = arr[1..4];

// 字符串切片
const msg: []const u8 = "Hello";

// 动态数组
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();

Go

// 固定大小数组
var arr [5]int = [5]int{1, 2, 3, 4, 5}

// 切片(动态数组)
slice := []int{1, 2, 3, 4, 5}
subslice := slice[1:4]

// 字符串
msg := "Hello"

// 动态数组(切片的底层)
list := make([]int, 0, 10)

函数

函数定义

Zig

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

// 多返回值
fn divide(a: i32, b: i32) struct { i32, i32 } {
    return .{ a / b, a % b };
}

// 泛型函数
fn max(comptime T: type, a: T, b: T) T {
    if (a > b) return a;
    return b;
}

Go

// 基本函数
func add(a int, b int) int {
    return a + b
}

// 多返回值
func divide(a int, b int) (int, int) {
    return a / b, a % b
}

// 泛型函数(Go 1.18+)
func max[T comparable](a T, b T) T {
    if a > b {
        return a
    }
    return b
}

关键差异

  • Zig 使用 fn 关键字
  • Zig 支持编译时泛型参数 comptime T: type
  • Zig 使用匿名结构体返回多个值
  • Go 使用 func 关键字
  • Go 直接在函数签名中定义多返回值
  • Go 的泛型语法相对较新

控制流

条件语句

Zig

const x = 10;

if (x > 5) {
    std.debug.print("大于5\n", .{});
} else if (x == 5) {
    std.debug.print("等于5\n", .{});
} else {
    std.debug.print("小于5\n", .{});
}

// switch 语句
switch (x) {
    0...5 => std.debug.print("0-5\n", .{}),
    6...10 => std.debug.print("6-10\n", .{}),
    else => std.debug.print("其他\n", .{}),
}

Go

x := 10

if x > 5 {
    fmt.Println("大于5")
} else if x == 5 {
    fmt.Println("等于5")
} else {
    fmt.Println("小于5")
}

// switch 语句
switch x {
case 0, 1, 2, 3, 4, 5:
    fmt.Println("0-5")
case 6, 7, 8, 9, 10:
    fmt.Println("6-10")
default:
    fmt.Println("其他")
}

循环

Zig

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

// for 循环
const arr = [_]i32{ 1, 2, 3, 4, 5 };
for (arr) |value| {
    std.debug.print("{d}\n", .{value});
}

// 带索引的循环
for (arr, 0..) |value, index| {
    std.debug.print("{}: {d}\n", .{ index, value });
}

Go

// for 循环(Go 只有 for)
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// range 循环
arr := []int{1, 2, 3, 4, 5}
for _, value := range arr {
    fmt.Println(value)
}

// 带索引的 range
for index, value := range arr {
    fmt.Printf("%d: %d\n", index, value)
}

内存管理

内存分配对比

Zig: 手动内存管理

const std = @import("std");

pub fn main() !void {
    // 创建分配器
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    
    const allocator = gpa.allocator();
    
    // 分配内存
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);
    
    // 使用内存
    @memcpy(buffer[0..5], "Hello");
    std.debug.print("{s}\n", .{buffer[0..5]});
}

Go: 自动垃圾回收

package main

import "fmt"

func main() {
    // 自动分配内存
    buffer := make([]byte, 1024)
    
    // 使用内存
    copy(buffer[0:5], "Hello")
    fmt.Println(string(buffer[0:5]))
    
    // GC 自动清理
}

内存管理策略对比

Zig 优势
  • • 零运行时开销
  • • 完全控制内存生命周期
  • • 适合系统编程
  • • 可预测的性能
Go 优势
  • • 简化开发
  • • 自动内存管理
  • • 减少内存泄漏
  • • 提高开发效率

错误处理

错误处理机制

Zig: 错误联合类型

const std = @import("std");

// 定义错误
const Error = error{
    DivisionByZero,
    Overflow,
};

// 返回错误联合类型
fn divide(a: i32, b: i32) Error!i32 {
    if (b == 0) {
        return Error.DivisionByZero;
    }
    return a / b;
}

pub fn main() !void {
    // 使用 try 传播错误
    const result = try divide(10, 2);
    
    // 显式错误处理
    const err_result = divide(10, 0);
    if (err_result) |value| {
        std.debug.print("Success: {}\n", .{value});
    } else |err| {
        std.debug.print("Error: {}\n", .{err});
    }
}

Go: 多返回值错误处理

package main

import (
    "errors"
    "fmt"
)

// 定义错误
var ErrDivisionByZero = errors.New("division by zero")
var ErrOverflow = errors.New("overflow")

// 返回错误作为第二个值
func divide(a int, b int) (int, error) {
    if b == 0 {
        return 0, ErrDivisionByZero
    }
    return a / b, nil
}

func main() {
    // 显式错误处理
    result, err := divide(10, 2)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Success: %d\n", result)
}

错误处理哲学

Zig 方式
  • • 错误是类型系统的一部分
  • • 编译时错误检查
  • • 显式错误传播
  • • 无隐藏的控制流
Go 方式
  • • 错误是普通的值
  • • 运行时错误处理
  • • 显式错误检查
  • • 简单易理解

编译时计算

Zig 的 comptime 特性

const std = @import("std");

// 编译时计算斐波那契数列
fn fibonacci(comptime n: u32) u32 {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 编译时生成类型
fn Matrix(comptime T: type, comptime rows: usize, comptime cols: usize) type {
    return struct {
        data: [rows][cols]T,
        
        pub fn init() @This() {
            return @This(){
                .data = std.mem.zeroes([rows][cols]T),
            };
        }
        
        pub fn add(self: @This(), other: @This()) @This() {
            var result = @This().init();
            for (&result.data, self.data, other.data) |*r_row, s_row, o_row| {
                for (r_row, s_row, o_row) |*r, s, o| {
                    r.* = s + o;
                }
            }
            return result;
        }
    };
}

pub fn main() !void {
    // 编译时计算
    const fib10 = fibonacci(10);
    std.debug.print("Fibonacci(10) = {}\n", .{fib10});
    
    // 编译时生成类型
    const Mat3x3 = Matrix(f32, 3, 3);
    var m1 = Mat3x3.init();
    var m2 = Mat3x3.init();
    
    // 使用编译时生成的代码
    const m3 = m1.add(m2);
}

编译时计算的优势

  • 零运行时开销 - 计算在编译时完成
  • 类型生成 - 根据参数生成专门的类型
  • 代码优化 - 编译器可以进行更多优化
  • 元编程 - 强大的代码生成能力

Go 的等价实现

Go 没有编译时计算特性,需要通过代码生成或接口来实现类似功能:

// Go 需要使用代码生成工具或接口
// 无法在运行时生成专门的类型

type Matrix struct {
    Data [][]float64
    Rows int
    Cols int
}

func NewMatrix(rows, cols int) *Matrix {
    data := make([][]float64, rows)
    for i := range data {
        data[i] = make([]float64, cols)
    }
    return &Matrix{
        Data: data,
        Rows: rows,
        Cols: cols,
    }
}

// 运行时检查类型
func (m *Matrix) Add(other *Matrix) (*Matrix, error) {
    if m.Rows != other.Rows || m.Cols != other.Cols {
        return nil, errors.New("matrix dimensions don't match")
    }
    // 运行时计算...
}