概览
C 与 Rust 形成了许多 Zig 开发者带来的心智模型:手动malloc/free、RAII 析构、Option<T>、Result<T, E>与 trait 对象。本附录将这些习惯转换为地道的 Zig,以便你无需与语言“搏斗”就能移植真实代码库。
Zig 收紧的指针对齐规则(@alignCast)与更好的分配器诊断在包装外部 API 时频繁出现。v0.15.2
学习目标
- 用
defer/errdefer交换手动资源清理,同时保留你从 C 期望的控制。 - 以可组合的方式用 Zig 可选值和错误联合表达受 Rust 启发的
Option/Result逻辑。 - 将基于回调或 trait 的多态性适配到 Zig 的
comptime泛型和指针垫片。
翻译 C 的资源生命周期
C 程序员习惯性地将每个 malloc 与匹配的 free 配对。Zig 让你用 errdefer 和结构化错误集编码相同意图,使缓冲即使在验证失败时也从不泄漏。4 下面的示例对比了直接翻译与 Zig 优先的自动释放内存辅助函数,突出显示分配器错误如何与域错误组合。mem.zig
// ! 使用 Zig 基于 defer 的清理机制重新实现 C 风格的缓冲区复制。
const std = @import("std");
pub const NormalizeError = error{InvalidCharacter} || std.mem.Allocator.Error;
pub fn duplicateAlphaUpper(allocator: std.mem.Allocator, input: []const u8) NormalizeError![]u8 {
const buffer = try allocator.alloc(u8, input.len);
errdefer allocator.free(buffer);
for (buffer, input) |*dst, src| switch (src) {
'a'...'z', 'A'...'Z' => dst.* = std.ascii.toUpper(src),
else => return NormalizeError.InvalidCharacter,
};
return buffer;
}
pub fn cStyleDuplicateAlphaUpper(allocator: std.mem.Allocator, input: []const u8) NormalizeError![]u8 {
const buffer = try allocator.alloc(u8, input.len);
var ok = false;
defer if (!ok) allocator.free(buffer);
for (buffer, input) |*dst, src| switch (src) {
'a'...'z', 'A'...'Z' => dst.* = std.ascii.toUpper(src),
else => return NormalizeError.InvalidCharacter,
};
ok = true;
return buffer;
}
test "duplicateAlphaUpper releases buffer on failure" {
const allocator = std.testing.allocator;
try std.testing.expectError(NormalizeError.InvalidCharacter, duplicateAlphaUpper(allocator, "zig-0"));
}
test "c style duplicate succeeds with valid input" {
const allocator = std.testing.allocator;
const dup = try cStyleDuplicateAlphaUpper(allocator, "zig");
defer allocator.free(dup);
try std.testing.expectEqualStrings("ZIG", dup);
}
$ zig test 01_c_style_cleanup.zigAll 2 tests passed.显式的NormalizeError联合同时跟踪分配器失败与校验失败,这一模式在第 10 章的分配器巡礼中被广泛推荐。
映射 Rust 的 Option 与 Result 类型
Rust’s Option<T> maps cleanly to Zig’s ?T, while Result<T, E> becomes an error union (E!T) with rich tags instead of stringly typed messages. 4 This recipe pulls a configuration value from newline-separated text, first with an optional search and then with a domain-specific error union that converts parsing failures into caller-friendly diagnostics. fmt.zig
// ! 使用 Zig 的可选类型和错误联合体模仿 Rust 的 Option 和 Result 习惯用法。
const std = @import("std");
pub fn findPortLine(env: []const u8) ?[]const u8 {
var iter = std.mem.splitScalar(u8, env, '\n');
while (iter.next()) |line| {
if (std.mem.startsWith(u8, line, "PORT=")) {
return line["PORT=".len..];
}
}
return null;
}
pub const ParsePortError = error{
Missing,
Invalid,
};
pub fn parsePort(env: []const u8) ParsePortError!u16 {
const raw = findPortLine(env) orelse return ParsePortError.Missing;
return std.fmt.parseInt(u16, raw, 10) catch ParsePortError.Invalid;
}
test "findPortLine returns optional when key absent" {
try std.testing.expectEqual(@as(?[]const u8, null), findPortLine("HOST=zig-lang"));
}
test "parsePort converts parse errors into domain error set" {
try std.testing.expectEqual(@as(u16, 8080), try parsePort("PORT=8080\n"));
try std.testing.expectError(ParsePortError.Missing, parsePort("HOST=zig"));
try std.testing.expectError(ParsePortError.Invalid, parsePort("PORT=xyz"));
}
$ zig test 02_rust_option_result.zigAll 2 tests passed.由于 Zig 将“是否存在”的发现与错误传播分离,你可以复用findPortLine进行快速路径检查,同时让parsePort处理较慢且可能失败的工作——这对应于 Rust 将Option::map与Result::map_err分开的惯例。17
桥接 Trait 与函数指针
C 和 Rust 都依赖回调——带有上下文有效负载的原始函数指针或带有显式 self 参数的 trait 对象。Zig 用 *anyopaque 垫片加上 comptime 适配器建模相同的抽象,因此你可以保持类型安全和零成本间接寻址。33 下面的示例展示了一个 C 风格回调和一个类似 trait 的 handle 方法,通过相同的传统桥接复用,依赖于 Zig 的指针转换和对齐断言。builtin.zig
// ! 将 C 函数指针回调模式转换为类型安全的 Zig shim。
const std = @import("std");
pub const LegacyCallback = *const fn (ctx: *anyopaque) void;
fn callLegacy(callback: LegacyCallback, ctx: *anyopaque) void {
callLegacy(callback, ctx);
}
const Counter = struct {
value: u32,
};
fn incrementShim(ctx: *anyopaque) void {
const counter: *Counter = @ptrCast(@alignCast(ctx));
counter.value += 1;
}
pub fn incrementViaLegacy(counter: *Counter) void {
callLegacy(incrementShim, counter);
}
pub fn dispatchWithContext(comptime Handler: type, ctx: *Handler) void {
const shim = struct {
fn invoke(raw: *anyopaque) void {
const typed: *Handler = @ptrCast(@alignCast(raw));
Handler.handle(typed);
}
};
callLegacy(shim.invoke, ctx);
}
const Stats = struct {
total: u32 = 0,
fn handle(self: *Stats) void {
self.total += 2;
}
};
test "incrementViaLegacy integrates with C-style callback" {
var counter = Counter{ .value = 0 };
incrementViaLegacy(&counter);
try std.testing.expectEqual(@as(u32, 1), counter.value);
}
test "dispatchWithContext adapts trait-like handle method" {
var stats = Stats{};
dispatchWithContext(Stats, &stats);
try std.testing.expectEqual(@as(u32, 2), stats.total);
}
$ zig test 03_callback_bridge.zigAll 2 tests passed.额外的@alignCast调用反映了 0.15.2 的“地雷”——指针转换现在会断言对齐,因此在包装来自 C 库的*anyopaque句柄时请保留这些断言。v0.15.2