关于 zig-0.15
面向使用 Zig 0.15.x 的开发者,提供针对 0.15.2 的编程指南,涵盖标准库与构建系统的重大 API 变化及迁移要点。
主要功能
- 01Zig 0.15 API 变更说明
- 02std.Io.Reader 与 Writer 用法
- 03标准库 API 示例
- 04build.zig 迁移指南
- 05常见错误及修复方法
适用场景
- 01将旧版代码迁移到 Zig 0.15
- 02核对 ArrayList 与 HashMap 用法
- 03更新 I/O、HTTP 与加密代码
- 04排查构建脚本和编译错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zigcc/ skills --skill zig-0.15安装后在 Claude Code 中输入/zig-0.15确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
VERSIONING.mdREADME.mdscripts/check-zig-version.sh详细介绍
Zig 0.15.x 编程指南
版本范围:此技能固定使用 Zig 0.15.x(具体为 0.15.2)。 发行说明:https://ziglang.org/download/0.15.1/release-notes.html
优先查阅本地文档
运行 zig env 查看安装路径——切勿将路径硬编码。关键字段:.lib_dir、.std_dir、.version。
- 语言参考:
<lib_dir>/../doc/langref.html - 标准库源码:阅读
.std_dir下的文件以验证 API。
搜索网页前,务必先查阅本地文档。
Zig 0.15 中的重大 API 变更
ArrayList(破坏性变更)
现在,所有修改方法都需要显式传入 allocator 参数:
// ❌ WRONG (0.13 and earlier)
var list = std.ArrayList(T).init(allocator);
try list.append(item);
try list.appendSlice(items);
_ = try list.addOne();
_ = try list.toOwnedSlice();
// ✅ CORRECT (0.15+)
var list = try std.ArrayList(T).initCapacity(allocator, 16);
defer list.deinit(allocator);
try list.append(allocator, item);
try list.appendSlice(allocator, items);
_ = try list.addOne(allocator);
_ = try list.toOwnedSlice(allocator);
// AssumeCapacity variants do NOT need allocator
list.appendAssumeCapacity(item);
| 方法 | 0.13 | 0.15+ |
|---|---|---|
append | try list.append(item) | try list.append(allocator, item) |
appendSlice | try list.appendSlice(items) | try list.appendSlice(allocator, items) |
addOne | try list.addOne() | try list.addOne(allocator) |
ensureTotalCapacity | try list.ensureTotalCapacity(n) | try list.ensureTotalCapacity(allocator, n) |
toOwnedSlice | try list.toOwnedSlice() | try list.toOwnedSlice(allocator) |
deinit | list.deinit() | list.deinit(allocator) |
HashMap
托管型(内部存储分配器):
var map = std.StringHashMap(V).init(allocator);
defer map.deinit();
try map.put(key, value); // No allocator needed
非托管型(每次操作都需要分配器):
var map = std.StringHashMapUnmanaged(V){};
defer map.deinit(allocator);
try map.put(allocator, key, value); // Allocator required
Writergate:全新的 std.Io.Writer 和 std.Io.Reader(重大重写)
Zig 0.15 用基于具体类型和虚表的 std.Io.Reader / std.Io.Writer 替代了旧的泛型 std.io 读取器/写入器。与旧 API 的主要区别:
- 具体类型(而非
anytype)——可存储在结构体中,也可作为参数传递 - 由调用方提供缓冲区——不会进行隐藏分配
- 精确的错误类型——使用
error{ReadFailed}/error{WriteFailed},而非anyerror - 环形缓冲区设计——无需复制即可高效查看/读取数据
std.Io.Writer
// Core struct (caller provides buffer, vtable handles draining)
pub const Writer = struct {
vtable: *const VTable,
buffer: []u8, // caller-provided; buffer[0..end] = buffered data
end: usize = 0,
};
主要方法:
| 方法 | 用途 |
|---|---|
writeAll(bytes) | 写入所有字节(可能先缓冲,再排空缓冲区) |
writeByte(byte) | 写入单个字节 |
print(fmt, args) | 格式化写入 |
flush() | 排空所有缓冲数据 |
buffered() | 获取当前缓冲数据的切片 |
创建写入器:
// 1. Fixed buffer (in-memory, bounded)
var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try writer.print("count: {d}", .{42});
// written data: buf[0..writer.end]
// 2. Allocating (dynamic, growable)
var aw: std.Io.Writer.Allocating = .init(allocator);
defer aw.deinit();
try aw.writer.print("hello {s}", .{"world"});
const output = aw.written(); // []u8 view
const owned = try aw.toOwnedSlice(); // caller owns
// 3. From file
var file_buf: [4096]u8 = undefined;
var file_writer = file.writer(&file_buf);
try file_writer.interface.writeAll("data\n");
try file_writer.interface.flush();
// 4. stdout / stderr
var stdout_buf: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buf);
const stdout: *std.Io.Writer = &stdout_writer.interface;
try stdout.print("Hello\n", .{});
try stdout.flush(); // Don't forget!
std.Io.Reader
// Core struct (caller provides buffer, vtable handles filling)
pub const Reader = struct {
vtable: *const VTable,
buffer: []u8, // caller-provided; buffer[seek..end] = buffered data
seek: usize, // bytes consumed
end: usize, // bytes available
};
主要方法:
| 方法 | 用途 |
|---|---|
take(n) | 恰好读取 n 个字节,并推进位置 |
takeByte() | 读取一个字节 |
takeInt(T, endian) | 按指定字节序读取整数 |
takeDelimiterExclusive(delim) | 读取直到分隔符(不包含分隔符) |
takeDelimiterInclusive(delim) | 读取直到分隔符(包含分隔符) |
peek(n) | 查看接下来的 n 个字节,但不消耗它们 |
peekByte() | 查看一个字节 |
readSliceAll(buf) | 从流中填满整个缓冲区 |
allocRemaining(gpa, limit) | 将所有剩余内容读入已分配的切片 |
stream(writer, limit) | 将数据传输到 Writer |
streamRemaining(writer) | 将所有数据传输到文件末尾(EOF) |
discard(limit) | 跳过字节而不读取 |
buffered() | 获取当前缓冲数据的切片 |
创建读取器:
// 1. Fixed buffer (in-memory, read-only)
var reader: std.Io.Reader = .fixed("line1\nline2\n");
// 2. From file
var read_buf: [4096]u8 = undefined;
var file_reader = file.reader(&read_buf);
const data = try file_reader.interface.take(10);
// 3. Limited (wrap existing reader with byte limit)
var limit_buf: [256]u8 = undefined;
var limited = reader.limited(.limited(100), &limit_buf);
// limited.interface reads at most 100 bytes
逐行读取:
while (reader.takeDelimiterExclusive('\n')) |line| {
// process line (delimiter not included)
} else |err| switch (err) {
error.EndOfStream => {},
else => return err,
}
将全部内容读入已分配的切片:
const contents = try reader.allocRemaining(allocator, .limited(1 << 20));
defer allocator.free(contents);
std.Io.Limit
控制流或读取操作消耗的字节数:
const limit = std.Io.Limit.limited(1024); // at most 1024 bytes
const unlimited = std.Io.Limit.unlimited; // no limit
const nothing = std.Io.Limit.nothing; // zero bytes
Reader ↔ Writer:流式传输
// Pump all data from reader to writer
_ = try reader.streamRemaining(&writer);
// Pump with byte limit
_ = try reader.stream(&writer, .limited(4096));
File.Reader / File.Writer
std.fs.File 会返回包装结构体;使用 .interface 字段获取 std.Io.Reader / std.Io.Writer:
var write_buf: [4096]u8 = undefined;
var file_writer = file.writer(&write_buf);
try file_writer.interface.writeAll("hello\n"); // .interface is *std.Io.Writer
try file_writer.interface.flush();
var read_buf: [4096]u8 = undefined;
var file_reader = file.reader(&read_buf);
const line = try file_reader.interface.takeDelimiterExclusive('\n');
有两种模式:file.writer(buf)(按位置操作,线程安全)和 file.writerStreaming(buf)(顺序操作)。条件允许时,优先使用按位置操作的模式。
从旧版 std.io 迁移
| 旧版(0.14) | 新版(0.15) |
|---|---|
std.io.getStdOut().writer() | std.fs.File.stdout().writer(&buf) → .interface |
std.io.fixedBufferStream(&buf) | std.Io.Writer.fixed(&buf) / std.Io.Reader.fixed(data) |
fbs.getWritten() | writer.buffered() |
writer.writeAll(data) | 相同,但作用于 *std.Io.Writer |
GenericWriter / AnyWriter | *std.Io.Writer(具体类型) |
GenericReader / AnyReader | *std.Io.Reader(具体类型) |
reader.readAll(buf) | reader.readSliceAll(buf) |
reader.readUntilDelimiter(delim, buf) | reader.takeDelimiterExclusive(delim) |
桥接适配器 — 如果仍在使用旧式 GenericWriter,且需要新接口:
fn useOldWriter(old_writer: anytype) !void {
var adapter = old_writer.adaptToNewApi(&.{});
const w: *std.Io.Writer = &adapter.new_interface;
try w.print("{s}", .{"example"});
}
HTTP 客户端(重大重写)
// High-level fetch()
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
const result = try client.fetch(.{
.location = .{ .url = "https://example.com/api" },
.method = .GET,
});
// Low-level request/response
const uri = try std.Uri.parse("https://example.com/api");
var req = try client.request(.POST, uri, .{
.extra_headers = &.{
.{ .name = "Content-Type", .value = "application/json" },
},
});
defer req.deinit();
try req.sendBodyComplete(@constCast("{\"key\": \"value\"}"));
var redirect_buf: [4096]u8 = undefined;
var response = try req.receiveHead(&redirect_buf);
// Read body via std.Io.Reader
var body_reader = response.reader(&redirect_buf);
const body = try body_reader.allocRemaining(allocator, .limited(1 << 20));
defer allocator.free(body);
Base64
// ❌ WRONG (0.13) — std.base64.standard.encode / .decode
// ✅ CORRECT (0.15+) — use .Encoder / .Decoder sub-namespace
// Encode
const encoded = std.base64.standard.Encoder.encode(&buf, data);
// Decode
const decoded = try std.base64.standard.Decoder.decode(&out_buf, encoded);
// URL-safe variant: std.base64.url_safe.Encoder / .Decoder
Ed25519 密码学(重大变更)
密钥类型现在是结构体,而不是原始字节数组:
const Ed25519 = std.crypto.sign.Ed25519;
// ❌ WRONG (0.13 - SecretKey was [32]u8)
const secret: [32]u8 = ...;
const kp = Ed25519.KeyPair.fromSecretKey(secret);
// ✅ CORRECT (0.15+ - SecretKey is struct with 64 bytes)
// From 32-byte seed (deterministic):
const seed: [32]u8 = ...;
const kp = try Ed25519.KeyPair.generateDeterministic(seed);
// From 64-byte secret key:
var secret_bytes: [64]u8 = ...;
const secret_key = try Ed25519.SecretKey.fromBytes(secret_bytes);
const kp = try Ed25519.KeyPair.fromSecretKey(secret_key);
// Get public key bytes:
const pubkey_bytes: [32]u8 = kp.public_key.toBytes();
// Get seed:
const seed: [32]u8 = kp.secret_key.seed();
签名变更:
// ❌ WRONG (0.13 - fromBytes returned error union)
const sig = try Ed25519.Signature.fromBytes(bytes);
// ✅ CORRECT (0.15+ - fromBytes does NOT return error)
const sig = Ed25519.Signature.fromBytes(bytes);
类型内省枚举(大小写变更)
// ❌ WRONG (0.13 - PascalCase)
if (@typeInfo(T) == .Slice) { ... }
if (@typeInfo(T) == .Pointer) { ... }
if (@typeInfo(T) == .Struct) { ... }
// ✅ CORRECT (0.15+ - lowercase/escaped)
if (@typeInfo(T) == .slice) { ... }
if (@typeInfo(T) == .pointer) { ... }
if (@typeInfo(T) == .@"struct") { ... }
if (@typeInfo(T) == .@"enum") { ... }
if (@typeInfo(T) == .@"union") { ... }
if (@typeInfo(T) == .array) { ... }
if (@typeInfo(T) == .optional) { ... }
自定义格式化函数
// ❌ WRONG (0.13 - used {} format specifier)
pub fn format(
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
try writer.writeAll("...");
}
// Usage: std.fmt.bufPrint(&buf, "{}", .{value});
// ✅ CORRECT (0.15+ - use {f} format specifier)
pub fn format(self: Self, writer: anytype) !void {
_ = self;
try writer.writeAll("...");
}
// Usage: std.fmt.bufPrint(&buf, "{f}", .{value});
JSON 解析与序列化
const MyStruct = struct {
name: []const u8,
value: u32,
};
// ✅ CORRECT: Parsing (0.15+)
const json_str =
\\{"name": "test", "value": 42}
;
const parsed = try std.json.parseFromSlice(MyStruct, allocator, json_str, .{});
defer parsed.deinit();
const data = parsed.value;
// ✅ CORRECT: Serialization (0.15.2 - writer-based API)
// Note: There is NO stringifyAlloc in 0.15.2!
// Method 1: Using std.json.Stringify with Allocating writer
var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
var stringify: std.json.Stringify = .{
.writer = &out.writer,
.options = .{},
};
try stringify.write(data);
const json_output = out.written();
// Method 2: Using std.fmt with json.fmt wrapper
const formatted = try std.fmt.allocPrint(allocator, "{f}", .{std.json.fmt(data, .{})});
defer allocator.free(formatted);
// Method 3: To fixed buffer
var buf: [1024]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
var writer = fbs.writer();
var stringify2: std.json.Stringify = .{
.writer = &writer.adaptToNewApi(&.{}).new_interface,
.options = .{},
};
try stringify2.write(data);
const output = fbs.getWritten();
// Parse options
const parsed2 = try std.json.parseFromSlice(MyStruct, allocator, json_str, .{
.allocate = .alloc_always,
.ignore_unknown_fields = true,
.max_value_len = 1 << 20,
});
常见错误消息及修复方法
| 错误 | 原因 | 修复方法 |
|---|---|---|
expected 2 argument(s), found 1 | ArrayList 方法缺少分配器 | 将分配器作为第一个参数添加 |
no field or member function named 'encode' | 使用了 std.base64.standard.encode | 使用 std.base64.standard.Encoder.encode |
no field or member function named 'open' | 使用了旧版 HTTP API | 使用 client.request() 或 client.fetch() |
expected type 'SecretKey', found '[32]u8' | Ed25519 SecretKey 现在是结构体 | 使用 generateDeterministic(seed) |
expected error union type, found 'Signature' | Signature.fromBytes 不会返回错误 | 移除 try |
enum has no member named 'Slice' | @typeInfo 枚举项已变更 | 使用小写 .slice |
no field named 'root_source_file' | 旧版 build.zig API | 使用 root_module = b.createModule(...) |
构建系统(build.zig)变更(重大重写)
官方指南:https://ziglang.org/learn/build-system/
创建可执行文件
// ❌ WRONG (0.14 and earlier)
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
// ✅ CORRECT (0.15+)
const exe = b.addExecutable(.{
.name = "hello",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
创建库
// ❌ WRONG (0.14 - addSharedLibrary/addStaticLibrary)
const lib = b.addSharedLibrary(.{
.name = "mylib",
.root_source_file = .{ .path = "src/lib.zig" },
});
// ✅ CORRECT (0.15+ - unified addLibrary with linkage)
const lib = b.addLibrary(.{
.name = "mylib",
.linkage = .dynamic, // or .static
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
}),
});
路径处理
// ❌ WRONG (0.14 - .path field)
.root_source_file = .{ .path = "src/main.zig" },
// ✅ CORRECT (0.15+ - use b.path())
.root_source_file = b.path("src/main.zig"),
模块依赖项
// ❌ WRONG (0.14)
exe.addModule("sdk", sdk_module);
// ✅ CORRECT (0.15+)
exe.root_module.addImport("sdk", sdk_module);
测试
// ✅ CORRECT (0.15+)
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = b.graph.host, // use b.graph.host for native target
}),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
完整的 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 = "hello",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(exe);
const run_exe = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_exe.step);
}
语言变更
移除 usingnamespace
Zig 0.15 已完全移除 usingnamespace 关键字。
// ❌ WRONG (removed in 0.15)
pub usingnamespace @import("other.zig");
// ✅ CORRECT - explicit re-exports
pub const foo = @import("other.zig").foo;
pub const bar = @import("other.zig").bar;
// ✅ CORRECT - namespace via field
pub const other = @import("other.zig");
// Usage: other.foo, other.bar
混入的迁移方法:使用零位字段和 @fieldParentPtr:
// ❌ OLD mixin pattern
pub const Foo = struct {
count: u32 = 0,
pub usingnamespace CounterMixin(Foo);
};
// ✅ NEW mixin pattern (0.15+)
pub fn CounterMixin(comptime T: type) type {
return struct {
pub fn increment(m: *@This()) void {
const x: *T = @alignCast(@fieldParentPtr("counter", m));
x.count += 1;
}
};
}
pub const Foo = struct {
count: u32 = 0,
counter: CounterMixin(Foo) = .{}, // zero-bit field
};
// Usage: foo.counter.increment()
移除 async/await 关键字
async、await 和 @frameSize 已被移除。异步功能将通过标准库的新 I/O 接口提供。
// ❌ REMOVED - no async/await keywords
async fn fetchData() ![]u8 { ... }
const result = await fetchData();
// ✅ Use std.Io interfaces or threads instead
对 undefined 进行算术运算
对 undefined 执行可能触发非法行为的运算,现在会导致编译错误:
const a: u32 = 0;
const b: u32 = undefined;
// ❌ COMPILE ERROR in 0.15+
_ = a + b; // error: use of undefined value here causes illegal behavior
有损整数到浮点数的强制转换
如果整数无法精确表示,则会产生编译错误:
// ❌ COMPILE ERROR in 0.15+
const val: f32 = 123_456_789; // error: cannot represent precisely
// ✅ CORRECT - opt-in to floating-point rounding
const val: f32 = 123_456_789.0;