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へ移行
- 02ArrayListとHashMapの使い方を確認
- 03I/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 - 標準ライブラリのソース:API を確認するには、
.std_dir配下のファイルを読んでください。
ウェブ検索の前に、必ずローカルドキュメントを確認してください。
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
Managed(アロケーターを内部に保持):
var map = std.StringHashMap(V).init(allocator);
defer map.deinit();
try map.put(key, value); // No allocator needed
Unmanaged(操作ごとにアロケーターが必要):
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 リーダー/ライターが、具象型で vtable ベースの std.Io.Reader/std.Io.Writer に置き換えられています。従来の API との主な違い:
- 具象型(
anytypeではありません)— 構造体に格納したり、パラメーターとして渡したりできます - 呼び出し元がバッファーを提供 — 隠れたアロケーションはありません
- エラーが明確 —
anyerrorではなくerror{ReadFailed}/error{WriteFailed} - リングバッファー設計 — コピーせずに効率よく peek/take できます
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) | 1 バイトを書き込む |
print(fmt, args) | 書式付きで書き込む |
flush() | バッファー内のデータをすべて排出する |
buffered() | 現在バッファーにあるデータのスライスを取得する |
Writer の作成:
// 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() | 1 バイトを読み取る |
takeInt(T, endian) | エンディアンを指定して整数を読み取る |
takeDelimiterExclusive(delim) | 区切り文字まで読み取る(区切り文字は含まない) |
takeDelimiterInclusive(delim) | 区切り文字まで読み取る(区切り文字を含む) |
peek(n) | 消費せずに次の n バイトを確認する |
peekByte() | 1 バイトを先読みする |
readSliceAll(buf) | ストリームからバッファー全体を埋める |
allocRemaining(gpa, limit) | 残りをすべて読み取り、確保したスライスに格納する |
stream(writer, limit) | データを Writer に送る |
streamRemaining(writer) | EOF までデータをすべてストリーム送信する |
discard(limit) | 読まずにバイトをスキップする |
buffered() | 現在バッファーにあるデータのスライスを取得する |
Reader の作成:
// 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
1 行ずつ読み取る:
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 はラッパー構造体を返します。std.Io.Reader/std.Io.Writer を取得するには、.interface フィールドを使用してください。
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');
モードは 2 種類あります: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 の削除
usingnamespace キーワードは Zig 0.15 で完全に削除されました。
// ❌ 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;