一举两得学编程:Rust 与 Zig 对比学习教程

1. 语言哲学与定位

虽然 Rust 和 Zig 都致力于编写高效、可靠的系统级软件,但它们的侧重点和实现路径有所不同。

  • Rust: 安全至上,零成本抽象。其核心是通过所有权系统借用检查器生命周期在编译期确保内存安全和并发安全,无需垃圾回收(GC)。它提供了强大的抽象能力,但学习曲线相对陡峭。
  • Zig: 简单透明,极致控制。强调代码的可读性简单性以及与 C 语言的无缝互操作。它追求编译速度的提升(Zig 0.15.1 在调试模式下编译速度比之前版本快了约 5 倍),并给予程序员对内存管理和底层细节的完全控制。

这个表格总结了它们的主要设计哲学差异:

特性

Rust

Zig

内存安全

通过所有权、借用、生命周期等在编译时保证

通过严格的编译时检查等机制避免内存安全问题,但更依赖程序员

并发模型

强大且丰富(async/await, 线程, 通道, 锁等)

内置并发支持,但相对简单,目前正在演进(0.15.1 移除了 async/await 关键字,为新的异步 I/O 接口让路)

抽象程度

,零成本抽象

,偏向显式和控制

编译速度

尚可,但有时较慢(尤其增量编译)

极快(0.15.1 调试模式编译速度提升 5 倍)

学习曲线

陡峭

相对平缓(尤其对有 C 背景者)

主要适用场景

系统编程、Web 后端、命令行工具、网络服务、嵌入式、区块链等

系统编程、命令行工具、游戏开发、操作系统内核、编译器等领域

包管理/构建系统

Cargo(功能极其强大,生态丰富)

内置构建系统(简单直观,与语言紧密集成)

接下来,我们开始动手配置环境。

2. 开发环境设置

Rust 安装 (最新稳定版: 1.89.0)

# 使用 rustup 安装工具链
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

# 验证安装
rustc --version # 应输出 rustc 1.89.0 (...)
cargo --version

Zig 安装 (最新版本: 0.15.1)

从 Zig 官网下载对应预编译二进制包
https://ziglang.org/download/,解压并添加到 PATH 环境变量。

# 例如,在 Linux x86_64 上
wget https://ziglang.org/builds/zig-linux-x86_64-0.15.1.tar.xz
tar xf zig-linux-x86_64-0.15.1.tar.xz
sudo mv zig-linux-x86_64-0.15.1 /usr/local/zig
echo 'export PATH="/usr/local/zig:$PATH"' >> ~/.bashrc
source ~/.bashrc

# 验证安装
zig version # 应输出 0.15.1

编辑器设置

  • VSCode: 安装 rust-analyzer (Rust) 和 Zig Language (Zig) 扩展。
  • 其他编辑器: 如 NeoVim, IntelliJ IDEA 等也有很好的插件支持。

3. 基础语法对比

让我们从最基本的 "Hello, World!" 开始感受两者的语法差异。

3.1 Hello, World!

Rust

// Rust 使用宏(macro)进行输出,语句以分号结尾
fn main() {
    println!("Hello, World!");
}
  • fn main(): 定义主函数。
  • println!: 是一个(以 ! 结尾),用于格式化输出到控制台。
  • ;: 语句结尾的分号在 Rust 中是必须的

Zig

// Zig 需要显式导入标准库,语句可省略分号(但推荐加上)
const std = @import("std"); // 导入标准库

pub fn main() !void { // 主函数可以返回错误
    std.debug.print("Hello, World!\n", .{}); // .{} 是一个空的匿名结构体,用于传递格式化参数
}
  • const std = @import("std");: 显式导入标准库并赋值给常量 std。Zig 中几乎所有东西都必须是显式的。
  • pub fn main() !void: pub 表示函数公开。!void 是错误联合类型,表示此函数可能失败并返回错误,或者成功返回 void(无值)。
  • std.debug.print: 使用标准库的 debug 命名空间中的 print 函数。\n 用于换行。
  • .{}: 是一个匿名结构体字面量,在这里因为 print 不需要额外的参数,所以是空的。

3.2 变量与常量

两者都使用 let (Rust) / var (Zig) 声明变量,const 声明常量,但可变性(mutability)的默认态度不同。

Rust: 默认不可变

fn main() {
    let x = 5; // 不可变变量
    // x = 6; // 错误:不能对不可变变量赋值

    let mut y = 10; // 可变变量 (需要 `mut` 关键字)
    y = 15; // 正确

    const MAX_POINTS: u32 = 100_000; // 常量,类型必须标注,命名惯例是全大写加下划线
    // const 必须在编译时已知,且不能 shadowing
}
  • Shadowing: Rust 允许使用 let 再次声明同名变量,新变量会遮蔽(shadow)旧的。
    let spaces = "   ";
    let spaces = spaces.len(); // 类型从 &str 变为 usize,这是允许的

Zig: 显式可变性,更接近 C

const std = @import("std");

pub fn main() !void {
    var x: i32 = 5; // 可变变量,类型必须标注 (i32)
    x = 6; // 正确 (Zig 变量默认是可变的?不,Zig 变量声明时就必须用 var 或 const 明确其可变性)

    const y = 10; // 不可变常量,类型可推断 (comptime_int)
    // y = 11; // 错误

    const MAX_POINTS: u32 = 100000; // 常量,类型标注

    // Zig 不允许 shadowing 外层作用域的标识符
    // var x: i32 = 42; // 错误:重定义 'x'
}
  • Zig 中,var 和 const 在声明时就必须明确,变量的可变性由其声明关键字决定。Zig 不允许遮蔽(shadowing)外层作用域的变量名。

3.3 基本数据类型

两者都提供了丰富的整数、浮点数、布尔值和字符类型。

Rust

fn main() {
    // 标量类型
    let a: i32 = 42;      // 有符号 32 位整数
    let b: u64 = 42;      // 无符号 64 位整数
    let c: f64 = 3.14;    // 64 位浮点数 (默认)
    let d: bool = true;   // 布尔值
    let e: char = '';   // Unicode 标量值 (4字节)

    // 复合类型
    let tup: (i32, f64, u8) = (500, 6.4, 1); // 元组
    let (x, y, z) = tup; // 解构
    let five_hundred = tup.0; // 通过索引访问

    let arr: [i32; 5] = [1, 2, 3, 4, 5]; // 数组,固定长度
    let first = arr[0];
}

Zig

const std = @import("std");

pub fn main() !void {
    // 标量类型
    var a: i32 = 42;      // 有符号 32 位整数
    var b: u64 = 42;      // 无符号 64 位整数
    var c: f64 = 3.14;    // 64 位浮点数
    var d: bool = true;   // 布尔值
    var e: u8 = 'A';      // 字符 (本质是字节)

    // Zig 对 Unicode 字符有更明确的处理,通常使用数组或切片表示字符串
    const unicode_char: []const u8 = ""; // 字符串切片表示 Unicode

    // 复合类型
    var tup: struct { i32, f64, u8 } = .{ 500, 6.4, 1 }; // 匿名结构体作元组
    var x = tup.0; // 通过索引访问

    var arr: [5]i32 = [_]i32{1, 2, 3, 4, 5}; // 数组,固定长度
    var first = arr[0];
}
  • 字符类型: Rust 的 char 是 4 字节 Unicode 标量值。Zig 没有单独的 char 类型,通常用 u8 表示 ASCII,用 []const u8 (UTF-8 字符串切片) 处理文本。
  • 元组: Rust 有内置的元组类型。Zig 使用匿名结构体来模拟元组。
  • 数组: 两者都是固定长度、栈上分配。

4. 控制流

4.1 条件语句

Rust

fn main() {
    let number = 7;

    if number < 5 {
        println!("条件为真");
    } else {
        println!("条件为假");
    }

    // if 是表达式,可以返回值 (分支必须是相同类型)
    let result = if number > 5 { "大" } else { "小" };
    println!("数字是 {}", result);
}

Zig

const std = @import("std");

pub fn main() !void {
    const number = 7;

    if (number < 5) {
        std.debug.print("条件为真\n", .{});
    } else {
        std.debug.print("条件为假\n", .{});
    }

    // Zig 的 if 也是表达式
    const result = if (number > 5) "大" else "小";
    std.debug.print("数字是 {s}\n", .{result});
}
  • 语法非常相似。Rust 的条件不需要括号(但推荐加),Zig 的条件需要括号。

4.2 循环

Rust

fn main() {
    // `while` 循环
    let mut counter = 0;
    while counter < 5 {
        println!("{}", counter);
        counter += 1;
    }

    // `for` 循环遍历迭代器 (如范围、数组)
    for i in 0..5 { // 范围 0 <= i < 5
        println!("{}", i);
    }

    let arr = [10, 20, 30, 40, 50];
    for element in arr.iter() {
        println!("值是: {}", element);
    }

    // `loop` 无限循环
    let mut count = 0;
    loop {
        count += 1;
        if count == 3 {
            break;
        }
    }
}

Zig

const std = @import("std");

pub fn main() !void {
    // `while` 循环
    var counter: i32 = 0;
    while (counter < 5) : (counter += 1) { // `: (counter += 1)` 是循环继续表达式
        std.debug.print("{}\n", .{counter});
    }

    // `for` 循环遍历数组或切片
    const arr = [_]i32{10, 20, 30, 40, 50};
    for (arr) |value, index| { // |元素值, 索引| (索引可省略)
        std.debug.print("索引: {}, 值: {}\n", .{index, value});
    }

    // 无限循环
    var count: u8 = 0;
    while (true) {
        count += 1;
        if (count == 3) break;
    }
}
  • while 循环: Zig 的 while 语法有一个特色:while (条件) : (继续表达式) { ... },继续表达式在每次循环体结束后执行。
  • for 循环: Rust 的 for 基于迭代器。Zig 的 for 直接遍历数组或切片,可以同时获取值和索引。

5. 函数

5.1 基本函数定义

Rust

fn add(x: i32, y: i32) -> i32 { // 参数类型和返回值类型必须标注
    x + y // 表达式结尾无分号,作为返回值
    // 也可以使用 `return x + y;`
}

fn main() {
    let sum = add(2, 3);
    println!("2 + 3 = {}", sum);
}

Zig

const std = @import("std");

fn add(x: i32, y: i32) i32 { // 参数类型和返回值类型必须标注
    return x + y;
}

pub fn main() !void {
    const sum = add(2, 3);
    std.debug.print("2 + 3 = {}\n", .{sum});
}
  • 函数定义语法类似。Rust 使用 -> 指定返回类型,Zig 直接写在参数列表后。

5.2 错误处理

这是两者设计哲学差异的显著体现。

Rust: Result<T, E> 枚举

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero".to_string()) // 返回错误
    } else {
        Ok(a / b) // 返回成功值
    }
}

fn main() {
    match divide(10.0, 2.0) { // 匹配 Result
        Ok(result) => println!("结果是: {}", result),
        Err(e) => println!("错误: {}", e),
    }

    // 简洁写法:unwrap (失败则 panic) 或 expect (带消息的 panic)
    // let result = divide(10.0, 0.0).unwrap(); // 会 panic!
    // let result = divide(10.0, 0.0).expect("Failed to divide"); // 会 panic 并打印消息

    // 传播错误:使用 `?` 运算符
    // let result = divide(10.0, 0.0)?; // 只能在返回 Result 的函数中使用
}
  • Rust 使用 Result 枚举强制程序员处理所有可能的错误。
  • ? 运算符用于简化错误传播。

Zig: 错误联合类型 (Error Union Types)

const std = @import("std");

fn divide(a: f64, b: f64) !f64 { // `!f64` 表示返回 f64 或 error
    if (b == 0.0) {
        return error.DivisionByZero; // 返回错误
    }
    return a / b;
}

pub fn main() !void { // 主函数也可以返回错误
    const result = divide(10.0, 2.0) catch |err| { // `catch` 处理错误
        std.debug.print("错误: {s}\n", .{@errorName(err)});
        return err; // 将错误返回给操作系统
    };
    std.debug.print("结果是: {}\n", .{result});

    // 也可以使用 try 或 `catch` 默认值
    // const result = try divide(10.0, 0.0); // 遇到错误直接返回 (propagate)
    // const result = divide(10.0, 0.0) catch 0; // 遇到错误提供默认值
}
  • Zig 使用 !T 类型表示可能错误或成功值。
  • catch 关键字用于捕获和处理错误。
  • try 关键字是 catch |err| return err 的简写,用于传播错误。

6. 内存管理

这是 Rust 和 Zig 最核心的区别所在。

6.1 Rust 的所有权系统

Rust 的核心创新,用于在编译期保证内存安全。

fn main() {
    let s1 = String::from("hello"); // String 类型,在堆上分配内存
    let s2 = s1; // 所有权从 s1 移动到 s2 (move semantics)
    // println!("{}", s1); // 错误!s1 不再拥有数据,无法使用

    let s3 = s2.clone(); // 显式深度克隆,s2 和 s3 都有效
    println!("s2 = {}, s3 = {}", s2, s3);

    // 借用 (Borrowing) - 创建引用
    let len = calculate_length(&s2); // 传递不可变引用
    println!("'{}' 的长度是 {}", s2, len); // s2 仍然可用

    let mut s4 = String::from("hello");
    change_string(&mut s4); // 传递可变引用
    println!("s4 = {}", s4);
}

fn calculate_length(s: &String) -> usize { // s 是对 String 的不可变引用
    s.len()
} // 这里 s 离开作用域,但因为它不拥有数据,所以什么也不会发生

fn change_string(s: &mut String) { // s 是对 String 的可变引用
    s.push_str(", world");
}
  • 所有权规则:
  1. Rust 中的每一个值都有一个被称为其 所有者 的变量。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃(内存被释放)。
  • 移动: 像 String、Vec 这样的类型在赋值时会发生所有权转移。
  • 克隆: 需要深度复制数据时使用 .clone()。
  • 借用:
    &T: 不可变引用,允许多个同时存在。
    &mut T: 可变引用,同一时间只能有一个,且不能与不可变引用同时存在。
  • 这些规则由借用检查器在编译期强制执行,彻底避免了数据竞争和悬垂指针。

6.2 Zig 的手动内存管理

Zig 强调显式和可控,内存管理通常需要程序员手动操作分配器。

const std = @import("std");

pub fn main() !void {
    // 1. 选择并初始化一个分配器
    // 通用分配器 (GeneralPurposeAllocator) 是一个好的默认选择,能检测内存泄漏
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator(); // 获取分配器实例
    // 确保在程序结束时检查内存泄漏并释放所有资源
    defer _ = gpa.deinit(); // `defer` 语句在作用域结束时执行

    // 2. 使用分配器分配内存
    const str = try allocator.dupe(u8, "hello"); // 在堆上分配并复制字符串
    // `try` 是因为分配可能失败 (内存不足)
    defer allocator.free(str); // 确保分配的内存最终被释放

    std.debug.print("字符串: {s}\n", .{str});

    // 3. 计算长度 (无需所有权系统)
    const len = calculateLength(str);
    std.debug.print("'{s}' 的长度是 {}\n", .{str, len});

    // 4. 动态数组 (ArrayList) 示例
    var list = std.ArrayList(i32).init(allocator);
    defer list.deinit(); // 释放 ArrayList 自身占用的内存

    try list.append(1);
    try list.append(2);
    try list.append(3);

    for (list.items) |item, index| {
        std.debug.print("list[{}] = {}\n", .{index, item});
    }
}

fn calculateLength(s: []const u8) usize { // 切片:一个指针和长度,表示字符串的一部分或全部
    return s.len;
}
  • 分配器: Zig 中几乎所有堆分配操作都需要显式传递一个分配器。这提供了极大的灵活性(可以使用不同的分配策略)。
  • defer: 用于确保资源(如内存、文件句柄)在作用域结束时被清理,类似于 Go 的 defer。
  • 切片: []const u8 是常见的字符串和数组视图类型,包含指针和长度,类似于 Rust 的切片。
  • 责任: 程序员必须负责正确释放分配的内存,否则会导致内存泄漏。工具(如 GeneralPurposeAllocator)可以帮助检测泄漏,但不会像 Rust 那样在编译期保证。

7. 结构体和枚举

7.1 结构体 (Structs)

Rust

struct User {
    username: String, // 字段
    email: String,
    sign_in_count: u64,
    active: bool,
}

// 使用 `impl` 块为结构体定义方法
impl User {
    // 关联函数 (类似于构造函数)
    fn new(username: String, email: String) -> User {
        User {
            username,
            email,
            sign_in_count: 0,
            active: true,
        }
    }

    // 方法,`&self` 表示不可变实例引用
    fn deactivate(&mut self) { // `&mut self` 表示可变实例引用
        self.active = false;
    }
}

fn main() {
    let mut user = User::new(String::from("john"), String::from("john@example.com"));
    user.deactivate();
    println!("用户 {} 的状态: {}", user.username, user.active);
}

Zig

const std = @import("std");

const User = struct { // 定义结构体类型
    username: []const u8, // 字段 (这里用字符串切片)
    email: []const u8,
    sign_in_count: u64,
    active: bool,

    // 方法可以直接在结构体定义中声明
    // 关联函数
    fn init(username: []const u8, email: []const u8) User {
        return User{
            .username = username,
            .email = email,
            .sign_in_count = 0,
            .active = true,
        };
    }

    // 方法,self 是第一个参数
    fn deactivate(self: *User) void { // 接受 User 的指针
        self.active = false;
    }
};

pub fn main() !void {
    var user = User.init("john", "john@example.com");
    user.deactivate(); // Zig 会自动为方法调用处理引用/指针
    std.debug.print("用户 {s} 的状态: {}\n", .{user.username, user.active});
}
  • 两者都支持将数据和方法组织在结构体中。
  • Rust 使用单独的 impl 块,Zig 的方法直接定义在结构体内。
  • Rust 的方法通过 &self、&mut self 明确指定接收者的不可变/可变借用。Zig 的方法通过 self: *User 这样的参数类型来指定是指针(可变)还是值(不可变拷贝)。

7.2 枚举 (Enums) 和模式匹配

Rust: 强大的枚举和模式匹配

enum IpAddr {
    V4(String), // 可携带数据
    V6(String),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 }, // 匿名结构体
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        // 使用 match 进行模式匹配
        match self {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to ({}, {})", x, y),
            Message::Write(text) => println!("Text message: {}", text),
            Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),
        }
    }
}

fn main() {
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let msg = Message::Write(String::from("hello"));
    msg.call();
}
  • Rust 的枚举非常强大,每个变体可以携带不同类型和数量的数据。
  • match 表达式必须穷尽所有可能,确保安全。

Zig: 标签联合 (Tagged Unions)

const std = @import("std");

// Zig 使用 `union(enum)` 实现带标签的联合,类似枚举变体带数据
const IpAddr = union(enum) {
    V4: []const u8,
    V6: []const u8,
};

const Message = union(enum) {
    Quit: void, // 即使没有数据,也需要一个类型,常用 void
    Move: struct { x: i32, y: i32 },
    Write: []const u8,
    ChangeColor: struct { r: i32, g: i32, b: i32 },

    fn call(self: Message) void {
        // 使用 switch 进行模式匹配
        switch (self) {
            .Quit => std.debug.print("Quit\n", .{}),
            .Move => |data| std.debug.print("Move to ({}, {})\n", .{data.x, data.y}), // 捕获数据
            .Write => |text| std.debug.print("Text message: {s}\n", .{text}),
            .ChangeColor => |colors| std.debug.print("Change color to ({}, {}, {})\n", .{colors.r, colors.g, colors.b}),
        }
    }
};

pub fn main() !void {
    const home = IpAddr{ .V4 = "127.0.0.1" };
    const msg = Message{ .Write = "hello" };
    msg.call();
}
  • Zig 使用 union(enum) 来实现类似 Rust 枚举变体带数据的功能。
  • 使用 switch 进行模式匹配,也需要保证穷尽性。
  • 语法上比 Rust 稍显繁琐。

8. 并发编程

8.1 Rust 的并发模型

Rust 利用所有权系统安全地实现并发。

use std::thread;
use std::sync::{Arc, Mutex};
use std::time::Duration;

fn main() {
    // 1. 使用线程
    let handle = thread::spawn(|| { //  spawn 一个新线程
        for i in 1..5 {
            println!("线程中的数字: {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..3 {
        println!("主线程中的数字: {}", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap(); // 等待子线程结束

    // 2. 使用共享状态 (Arc + Mutex)
    // Arc<T>: 原子引用计数智能指针,允许多线程间共享所有权
    // Mutex<T>: 互斥锁,确保一次只有一个线程可以访问数据
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter); // 增加引用计数
        let handle = thread::spawn(move || { // move 捕获所有权
            let mut num = counter.lock().unwrap(); // 获取锁
            *num += 1; // 解引用并修改值
        }); // 锁在这里自动释放
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("结果: {}", *counter.lock().unwrap());
}
  • 线程: thread::spawn 创建新线程。
  • 共享状态: Arc (Atomic Reference Counting) 用于多线程间的共享所有权,Mutex (Mutual Exclusion) 用于内部可变性和同步。
  • 消息传递: 通常更推荐使用通道 (std::sync::mpsc) 进行线程间通信,但共享状态有时也是必要的工具。
  • 所有权和类型系统确保你不会错误地共享可变状态。

8.2 Zig 的并发模型

Zig 的并发仍在发展中,0.15.1 移除了 async/await 关键字,为新的异步 I/O 接口让路。目前更偏向于使用传统线程和显式同步。

const std = @import("std");

fn threadFunction() void {
    var i: u8 = 1;
    while (i < 5) : (i += 1) {
        std.debug.print("线程中的数字: {}\n", .{i});
        std.time.sleep(std.time.ns_per_ms * 1);
    }
}

pub fn main() !void {
    // 1. 创建线程
    const thread = try std.Thread.spawn(.{}, threadFunction, .{}); // 创建线程
    defer thread.join(); // 确保线程被等待 (join)

    var i: u8 = 1;
    while (i < 3) : (i += 1) {
        std.debug.print("主线程中的数字: {}\n", .{i});
        std.time.sleep(std.time.ns_per_ms * 1);
    }

    // 2. 使用原子操作 (Atomic)
    // Zig 推荐使用消息传递或原子操作,而不是互斥锁,但 Mutex 也存在
    var counter: std.atomic.Value(u32) = std.atomic.Value(u32).init(0);

    var threads: [10]std.Thread = undefined;
    for (&threads) |*t| {
        t.* = try std.Thread.spawn(.{}, struct {
            fn f(c: *std.atomic.Value(u32)) void {
                _ = c.fetchAdd(1, .SeqCst); // 原子加法
            }
        }.f, .{&counter});
    }

    for (threads) |t| {
        t.join();
    }

    std.debug.print("结果: {}\n", .{counter.load(.SeqCst)});
}
  • 线程: std.Thread.spawn 创建线程。
  • 原子操作: std.atomic.Value 提供原子操作,是轻量级同步的常用工具。
  • 异步: 新的异步 I/O 接口正在开发中,旨在作为标准库的一部分提供更灵活和强大的异步编程能力,而不再是语言关键字。

9. 实战项目:简单的 HTTP 服务器

让我们用一个简单的 HTTP 服务器来综合运用所学知识,直观感受两者的代码风格和生态差异。

Rust (使用hyper库)

  1. 编辑 Cargo.toml:
    [package]
    name = "rust-http-server"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    hyper = { version = "1.3", features = ["full"] }
    tokio = { version = "1.0", features = ["full"] }
  1. 编辑 src/main.rs:
    use hyper::service::{service_fn, make_service_fn};
    use hyper::{Body, Request, Response, Server, StatusCode};
    use std::convert::Infallible;
    use std::net::SocketAddr;

    async fn handle_request(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
        // 简单返回 "Hello, World!"
        Ok(Response::new(Body::from("Hello, World from Rust!")))
    }

    #[tokio::main] // 使用 tokio 异步运行时
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
        let make_svc = make_service_fn(|_conn| async {
            Ok::<_, Infallible>(service_fn(handle_request))
        });
        let server = Server::bind(&addr).serve(make_svc);
        println!("Rust HTTP server listening on http://{}", addr);
        server.await?;
        Ok(())
    }
  1. 运行:
    cargo run
    # 访问 http://127.0.0.1:3000
  • Rust 拥有丰富的生态系统(Cargo),可以轻松引入强大的库(hyper, tokio)。
  • 代码大量使用异步 (async/await)。

Zig (使用标准库std.http)

  1. 创建 build.zig (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 = "zig-http-server",
            .root_sourceFile = .{ .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);
    }
  1. 编辑 src/main.zig:
    const std = @import("std");

    pub fn main() !void {
        // 初始化分配器
        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        defer _ = gpa.deinit();
        const allocator = gpa.allocator();

        // 创建 HTTP 服务器
        var server = std.http.Server.init(allocator, .{ .reuse_address = true });
        defer server.deinit();

        const addr = std.net.Address.parseIp("127.0.0.1", 3000) catch unreachable;
        try server.listen(addr);

        std.debug.print("Zig HTTP server listening on http://127.0.0.1:3000\n", .{});

        // 处理请求
        while (true) {
            var response = try server.accept(.{ .allocator = allocator });
            defer response.deinit(); // 确保响应被清理

            // 等待并解析客户端请求头
            try response.wait(); 
            // 检查请求方法和路径 (这是一个非常简单的示例)
            // 在实际应用中,你需要更完整的路由和解析

            // 设置响应头
            try response.headers.append("Content-Type", "text/plain; charset=utf-8");
            
            // 写入响应体
            const writer = response.writer();
            try writer.writeAll("Hello, World from Zig!");
            
            // 完成响应
            try response.finish();
        }
    }
  1. 运行:
    zig build run
    # 或者直接编译运行: zig run src/main.zig
    # 访问 http://127.0.0.1:3000
  • Zig 使用其内置的标准库 (std.http) 来处理 HTTP,无需额外依赖。
  • 代码相对更底层,需要手动处理更多细节(如分配器、请求/响应生命周期)。
  • 构建系统是 Zig 语言的一部分,通过 build.zig 文件配置。

这个简单的例子展示了:

  • Rust: 通过强大的生态系统(Cargo + crates)快速构建复杂应用,异步编程成熟。
  • Zig: 自包含的标准库,显式的资源管理,代码更接近底层,控制力更强。

10. 总结与进阶学习

10.1 核心差异回顾

特性

Rust

Zig

内存安全

编译时通过所有权/借用保证

依赖程序员的显式管理和工具(如分配器)

并发模型

丰富 (async/await, 线程, 通道, 锁)

发展中 (传统线程, 新的异步I/O正在开发)

抽象程度

( Traits, 智能指针, 宏)

(强调显式和简单,编译时执行)

包管理/构建

Cargo (极其强大和成熟)

内置构建系统 (简单,与语言集成)

学习曲线

陡峭 (所有权、生命周期)

相对平缓 (尤其对有C背景者),但概念独特

哲学

安全无需牺牲性能

简单、可读、可控

适用场景

复杂系统、高并发服务、安全关键应用

系统工具、编译器、内核、对控制要求高的场景

10.2 如何选择?

选择 Rust,如果:
- 你需要
绝对的内存安全和线程安全
- 项目复杂,需要
强大的抽象能力和丰富的生态系统
- 你需要构建
高性能、高并发的网络服务或应用程序
- 你愿意投入时间克服
陡峭的学习曲线

选择 Zig,如果:
- 你重视
代码的简单性、可读性和显式控制
- 你需要
极致的编译速度或与C代码的无缝互操作
- 你在编写
系统工具、编译器、嵌入式代码或操作系统内核
- 你希望
更精细地控制内存布局和分配策略
- 你偏好一种
更简单、更直接的语言。

理想情况:学习两者!它们代表了系统编程的不同优秀范式。理解 Rust 能让你深刻认识现代内存安全理念,而掌握 Zig 则能提升你对底层细节的控制能力和代码的简洁性。