目的
この記事では、Zigの基本構文を理解し、簡単なプログラムを書けるようになることを目指します。
対象読者:
- Rust経験者(特にRustシリーズを読んだ人)
- C/C++経験者
- システムプログラミングに興味がある人
この記事で学ぶこと:
- Zigの基本構文とRust/Cとの違い
- エラーハンドリング(try/catch)
- Optional型による安全なnull処理
- defer/errdefer によるリソース管理
- 構造体とメソッド
- テストの書き方
重要: Zigは「明示的であること」を重視します。隠れた制御フローがなく、すべてが明確です。
0. Zigとは何か
Zigの設計思想
Zigは「C言語の後継」を目指すシステムプログラミング言語です。
3つの核心原則:
-
No hidden control flow(隠れた制御フローなし)
- 演算子オーバーロードなし
- 例外なし
- デストラクタなし
- すべてが明示的
-
Explicit allocators(明示的なアロケータ)
- メモリ割り当てを明示
- Rustの所有権とは異なるアプローチ
-
Compile-time code execution(コンパイル時コード実行)
-
comptimeによる強力なメタプログラミング
-
Rust/Cとの比較
| 特徴 | C | Rust | Zig |
|---|---|---|---|
| メモリ安全性 | 手動 | 所有権システム | 明示的アロケータ |
| エラー処理 | errno, NULL | Result | error union (!T) |
| null安全性 | なし | Option | ?T |
| ジェネリクス | マクロ | トレイト | comptime |
| ビルドシステム | Make/CMake | Cargo | build.zig |
| C互換性 | ネイティブ | unsafe + FFI | @cImport |
Zigのユースケース:
- システムプログラミング(OS、ドライバ)
- 組み込みシステム
- CLIツール
- ゲームエンジン(Mach)
- ランタイム(Bun、Tigerbeetle)
- Cライブラリの置き換え
学習ポイント:
- Zigは「より良いC」
- Rustとは異なる安全性のアプローチ
- シンプルさと制御を重視
1. Hello World と開発環境
Zigのインストール
https://ziglang.org/download/ から最新版をダウンロード
最初のプログラム
// hello.zig
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, Zig!\n", .{});
}
# 実行
zig run hello.zig
# コンパイル
zig build-exe hello.zig
./hello
学習ポイント:
-
const std = @import("std")で標準ライブラリをインポート -
pub fn main()がエントリーポイント -
!voidはエラーを返す可能性がある(後述) -
tryでエラーを伝播
プロジェクトの作成
新しいプロジェクト
zig init
プロジェクト構成
my-project/
├── build.zig # ビルド設定
├── build.zig.zon # 依存関係
└── src/
├── main.zig # エントリーポイント
└── root.zig # ライブラリルート
ビルド
zig build
実行
zig build run
テスト
zig build test
学習ポイント:
-
zig initでプロジェクト作成 -
build.zigがビルドスクリプト(後述) -
zig buildでビルド
よくあるコンパイルエラー:
error: expected type 'void', found 'void!error{...}'
- 原因: エラーを返す関数を
tryなしで呼び出し - 対処:
tryを付ける
2. 変数宣言
var と const
const std = @import("std");
pub fn main() void {
// const: 不変(Rustの let)
const x: i32 = 10;
// x = 20; // ❌ エラー
// var: 可変(Rustの let mut)
var y: i32 = 10;
y = 20; // ✅ OK
std.debug.print("x = {}, y = {}\n", .{x, y});
}
型推論
pub fn main() void {
// 型推論(Rustと同様)
const x = 10; // i32 と推論
const pi = 3.14; // f64 と推論
const flag = true; // bool
// 明示的な型
const y: u8 = 255;
const z: f32 = 1.5;
}
comptime変数(軽く)
pub fn main() void {
// コンパイル時定数
comptime const x = 10;
comptime const y = x + 20;
// コンパイル時に計算される
const result = comptime fibonacci(10);
std.debug.print("fibonacci(10) = {}\n", .{result});
}
fn fibonacci(n: comptime_int) comptime_int {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Rustとの比較:
// Rust
let x = 10; // 不変
let mut y = 10; // 可変
const MAX: i32 = 100; // コンパイル時定数
// Zig
const x: i32 = 10; // 不変
var y: i32 = 10; // 可変
comptime const MAX = 100; // コンパイル時定数
学習ポイント:
-
constがデフォルト(Rustと同じ思想) -
varは最小限に -
comptimeはコンパイル時に評価
よくあるコンパイルエラー:
error: cannot assign to constant
- 原因:
const変数に再代入 - 対処:
varに変更
3. 基本的な型
整数型
pub fn main() void {
// 符号付き整数
const i8_val: i8 = -128;
const i16_val: i16 = -32768;
const i32_val: i32 = -2147483648;
const i64_val: i64 = -9223372036854775808;
// 符号なし整数
const u8_val: u8 = 255;
const u16_val: u16 = 65535;
const u32_val: u32 = 4294967295;
const u64_val: u64 = 18446744073709551615;
// 任意サイズの整数
const i3_val: i3 = -4; // -4 から 3
const u7_val: u7 = 127; // 0 から 127
// ポインタサイズ
const isize_val: isize = -100;
const usize_val: usize = 100;
}
浮動小数点型
pub fn main() void {
const f32_val: f32 = 3.14;
const f64_val: f64 = 3.141592653589793;
// 型推論ではf64
const pi = 3.14;
}
bool と文字
pub fn main() void {
const flag: bool = true;
const letter: u8 = 'A'; // ASCII文字
// Zigには文字型がない(u8を使う)
}
Rustとの違い:
| 型 | Rust | Zig |
|---|---|---|
| 文字 | char (Unicode) | u8 (ASCII) |
| 文字列 | String, &str | []const u8 |
| サイズ指定整数 | i8, i16, i32 | i8, i16, i32 |
| 任意サイズ | なし | i3, u7など |
学習ポイント:
- Zigは任意ビット幅の整数をサポート
- 文字は単に
u8 - 型推論は保守的(安全側に倒す)
4. 関数定義
基本的な関数
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
fn greet(name: []const u8) void {
std.debug.print("Hello, {s}!\n", .{name});
}
pub fn main() void {
const result = add(5, 3);
std.debug.print("5 + 3 = {}\n", .{result});
greet("Zig");
}
複数の戻り値(構造体を返す)
const DivResult = struct {
quotient: i32,
remainder: i32,
};
fn divmod(a: i32, b: i32) DivResult {
return DivResult{
.quotient = @divTrunc(a, b),
.remainder = @rem(a, b),
};
}
pub fn main() void {
const result = divmod(10, 3);
std.debug.print("10 / 3 = {}余り{}\n", .{result.quotient, result.remainder});
}
引数の扱い
fn increment(x: *i32) void {
x.* += 1;
}
fn process(slice: []const u8) void {
std.debug.print("Length: {}\n", .{slice.len});
}
pub fn main() void {
var x: i32 = 10;
increment(&x); // ポインタを渡す
std.debug.print("x = {}\n", .{x}); // 11
const data = "Hello";
process(data);
}
Rustとの比較:
// Rust
fn add(a: i32, b: i32) -> i32 {
a + b // 暗黙的なreturn
}
// Zig
fn add(a: i32, b: i32) i32 {
return a + b; // 明示的なreturn
}
学習ポイント:
-
fnで関数定義 -
pubで公開 - 戻り値の型は
>ではなく直接指定 -
returnは必須(暗黙的な戻り値なし)
よくあるコンパイルエラー:
error: expected type 'i32', found 'void'
- 原因: 戻り値の型と実際の戻り値が不一致
- 対処:
return文を追加または型を修正
5. 制御構文
if(式として扱える)
pub fn main() void {
const x = 10;
// if 文
if (x > 5) {
std.debug.print("x is greater than 5\n", .{});
} else {
std.debug.print("x is not greater than 5\n", .{});
}
// if 式(Rustと同じ)
const result = if (x > 5) "big" else "small";
std.debug.print("x is {s}\n", .{result});
// else if
if (x < 0) {
std.debug.print("negative\n", .{});
} else if (x == 0) {
std.debug.print("zero\n", .{});
} else {
std.debug.print("positive\n", .{});
}
}
while
pub fn main() void {
var i: i32 = 0;
while (i < 5) {
std.debug.print("{}\n", .{i});
i += 1;
}
// continue式付き
i = 0;
while (i < 5) : (i += 1) {
std.debug.print("{}\n", .{i});
}
}
for
pub fn main() void {
const items = [_]i32{ 1, 2, 3, 4, 5 };
// 要素のイテレート
for (items) |item| {
std.debug.print("{}\n", .{item});
}
// インデックス付き
for (items, 0..) |item, i| {
std.debug.print("[{}] = {}\n", .{i, item});
}
// 範囲(0.10 で初期化された配列と同じ)
for (0..5) |i| {
std.debug.print("{}\n", .{i});
}
}
switch(Rustのmatch相当)
pub fn main() void {
const value = 2;
// switch 式
const result = switch (value) {
1 => "one",
2 => "two",
3 => "three",
else => "other",
};
std.debug.print("value is {s}\n", .{result});
// 複数の値
const category = switch (value) {
1, 2, 3 => "small",
4, 5, 6 => "medium",
else => "large",
};
// 範囲
const range_result = switch (value) {
0...9 => "single digit",
10...99 => "double digit",
else => "large",
};
}
Rustとの比較:
// Rust: match
let result = match value {
1 => "one",
2 => "two",
_ => "other",
};
// Zig: switch
const result = switch (value) {
1 => "one",
2 => "two",
else => "other",
};
学習ポイント:
-
ifとswitchは式として使える -
forはイテレータではなく配列/スライス専用 -
switchは網羅性チェックあり(Rustと同じ) -
elseで残りをキャッチ
よくあるコンパイルエラー:
error: switch must handle all possibilities
- 原因:
switchで全パターンを網羅していない - 対処:
elseを追加
6. エラーハンドリング
エラーセット
const std = @import("std");
// エラーセットの定義
const MathError = error{
DivisionByZero,
Overflow,
NegativeValue,
};
// エラーユニオン(Rustの Result<T, E> 相当)
fn divide(a: i32, b: i32) MathError!i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
pub fn main() void {
const result = divide(10, 2) catch |err| {
std.debug.print("Error: {}\n", .{err});
return;
};
std.debug.print("Result: {}\n", .{result});
}
try と catch
const FileError = error{
FileNotFound,
PermissionDenied,
};
fn readFile(path: []const u8) FileError![]const u8 {
if (std.mem.eql(u8, path, "missing.txt")) {
return error.FileNotFound;
}
return "file contents";
}
fn processFile(path: []const u8) FileError!void {
// try でエラーを伝播(Rustの ? 演算子)
const contents = try readFile(path);
std.debug.print("Contents: {s}\n", .{contents});
}
pub fn main() void {
// catch でエラーをハンドル
processFile("data.txt") catch |err| {
std.debug.print("Failed to process file: {}\n", .{err});
};
// 特定のエラーをキャッチ
const result = readFile("missing.txt") catch |err| switch (err) {
error.FileNotFound => {
std.debug.print("File not found\n", .{});
return;
},
error.PermissionDenied => {
std.debug.print("Permission denied\n", .{});
return;
},
};
}
anyerror
// 任意のエラーを返せる
fn mightFail() anyerror!i32 {
return error.SomethingWentWrong;
}
// エラーセットを推論
fn autoInferError() !i32 {
if (false) return error.Failed;
return 42;
}
Rustとの比較:
// Rust
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
return Err("Division by zero".to_string());
}
Ok(a / b)
}
fn process() -> Result<i32, String> {
let result = divide(10, 0)?; // ? でエラー伝播
Ok(result)
}
// Zig
fn divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
fn process() !i32 {
const result = try divide(10, 0); // try でエラー伝播
return result;
}
学習ポイント:
-
errorでエラー値を定義 -
!Tはエラーユニオン(Tまたはerror) -
tryでエラーを伝播(Rustの?) -
catchでエラーをハンドル - Rustの
Resultより軽量
よくあるコンパイルエラー:
error: expected type 'i32', found 'error{DivisionByZero}!i32'
- 原因: エラーユニオンを
tryやcatchなしで使用 - 対処:
tryを付けるかcatchでハンドル
7. Optional型
?T の基本
const std = @import("std");
// Optional型(Rustの Option<T> 相当)
fn find(items: []const i32, target: i32) ?usize {
for (items, 0..) |item, i| {
if (item == target) return i;
}
return null;
}
pub fn main() void {
const numbers = [_]i32{ 10, 20, 30, 40, 50 };
// orelse でデフォルト値
const index1 = find(&numbers, 30) orelse 999;
std.debug.print("Index: {}\n", .{index1}); // 2
const index2 = find(&numbers, 99) orelse 999;
std.debug.print("Index: {}\n", .{index2}); // 999
// if でパターンマッチ
if (find(&numbers, 20)) |idx| {
std.debug.print("Found at index {}\n", .{idx});
} else {
std.debug.print("Not found\n", .{});
}
}
.? による強制アンラップ
pub fn main() void {
const maybe_value: ?i32 = 42;
// .? で強制アンラップ(nullならpanicのような挙動)
const value = maybe_value.?;
std.debug.print("Value: {}\n", .{value});
// nullの場合は実行時エラー
// const none: ?i32 = null;
// const bad = none.?; // ❌ panic
}
Optional型の組み合わせ
const User = struct {
name: []const u8,
email: ?[]const u8, // emailはオプショナル
};
fn getUserEmail(user: User) []const u8 {
return user.email orelse "no-email@example.com";
}
pub fn main() void {
const user1 = User{
.name = "Alice",
.email = "alice@example.com",
};
const user2 = User{
.name = "Bob",
.email = null,
};
std.debug.print("{s}: {s}\n", .{user1.name, getUserEmail(user1)});
std.debug.print("{s}: {s}\n", .{user2.name, getUserEmail(user2)});
}
Rustとの比較:
// Rust
fn find(items: &[i32], target: i32) -> Option<usize> {
items.iter().position(|&x| x == target)
}
// unwrap_or でデフォルト値
let index = find(&numbers, 30).unwrap_or(999);
// if let でパターンマッチ
if let Some(idx) = find(&numbers, 20) {
println!("Found at {}", idx);
}
// Zig
fn find(items: []const i32, target: i32) ?usize {
for (items, 0..) |item, i| {
if (item == target) return i;
}
return null;
}
// orelse でデフォルト値
const index = find(&numbers, 30) orelse 999;
// if でパターンマッチ
if (find(&numbers, 20)) |idx| {
std.debug.print("Found at {}\n", .{idx});
}
学習ポイント:
-
?TはTまたはnull -
orelseでデフォルト値を指定 -
.?で強制アンラップ(危険) -
ifでパターンマッチング
8. 配列とスライス
配列
pub fn main() void {
// 配列の宣言
const array1 = [5]i32{ 1, 2, 3, 4, 5 };
// 型推論([_] で要素数を推論)
const array2 = [_]i32{ 10, 20, 30 };
// 要素にアクセス
std.debug.print("array1[0] = {}\n", .{array1[0]});
// 長さ
std.debug.print("Length: {}\n", .{array1.len});
// 繰り返し初期化
const zeros = [_]i32{0} ** 10; // [0, 0, 0, ..., 0]
}
スライス
pub fn main() void {
const array = [_]i32{ 1, 2, 3, 4, 5 };
// 配列全体のスライス
const slice1: []const i32 = &array;
// 部分スライス
const slice2 = array[1..4]; // [2, 3, 4]
// 開始のみ指定
const slice3 = array[2..]; // [3, 4, 5]
for (slice2) |item| {
std.debug.print("{}\n", .{item});
}
}
文字列(スライス)
pub fn main() void {
// 文字列リテラル([]const u8)
const str1: []const u8 = "Hello, Zig!";
// 型推論
const str2 = "Hello, World!";
// 文字列の長さ
std.debug.print("Length: {}\n", .{str1.len});
// 文字列の比較
if (std.mem.eql(u8, str1, str2)) {
std.debug.print("Equal\n", .{});
} else {
std.debug.print("Not equal\n", .{});
}
// 部分文字列
const substr = str1[0..5]; // "Hello"
std.debug.print("{s}\n", .{substr});
}
可変スライス
pub fn main() void {
var array = [_]i32{ 1, 2, 3, 4, 5 };
// 可変スライス
const slice: []i32 = &array;
slice[0] = 10;
slice[1] = 20;
for (slice) |item| {
std.debug.print("{}\n", .{item});
}
}
Rustとの比較:
// Rust
let array: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &array[1..4];
let str: &str = "Hello";
// Zig
const array = [5]i32{ 1, 2, 3, 4, 5 };
const slice: []const i32 = array[1..4];
const str: []const u8 = "Hello";
学習ポイント:
- 配列:
[N]T(固定サイズ) - スライス:
[]T(可変サイズ) - 文字列:
[]const u8(バイト列) - Zigには
String型がない
9. 構造体
基本的な構造体
const std = @import("std");
const Point = struct {
x: f32,
y: f32,
// メソッド(実は関数)
pub fn init(x: f32, y: f32) Point {
return Point{ .x = x, .y = y };
}
pub fn distance(self: Point) f32 {
return @sqrt(self.x * self.x + self.y * self.y);
}
pub fn add(self: Point, other: Point) Point {
return Point{
.x = self.x + other.x,
.y = self.y + other.y,
};
}
};
pub fn main() void {
const p1 = Point.init(3.0, 4.0);
const p2 = Point{ .x = 1.0, .y = 2.0 };
std.debug.print("Distance: {}\n", .{p1.distance()});
const p3 = p1.add(p2);
std.debug.print("Sum: ({}, {})\n", .{p3.x, p3.y});
}
デフォルト値
const Config = struct {
host: []const u8 = "localhost",
port: u16 = 8080,
timeout: u32 = 30,
};
pub fn main() void {
// デフォルト値を使う
const config1 = Config{};
// 一部だけ指定
const config2 = Config{
.port = 3000,
};
std.debug.print("{}:{}\n", .{config1.host, config1.port});
std.debug.print("{}:{}\n", .{config2.host, config2.port});
}
ネストした構造体
const Address = struct {
street: []const u8,
city: []const u8,
};
const Person = struct {
name: []const u8,
age: u32,
address: Address,
};
pub fn main() void {
const person = Person{
.name = "Alice",
.age = 30,
.address = Address{
.street = "123 Main St",
.city = "Springfield",
},
};
std.debug.print("{s} lives in {s}\n", .{person.name, person.address.city});
}
Rustとの比較:
// Rust
struct Point {
x: f32,
y: f32,
}
impl Point {
fn new(x: f32, y: f32) -> Self {
Point { x, y }
}
fn distance(&self) -> f32 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
// Zig
const Point = struct {
x: f32,
y: f32,
pub fn init(x: f32, y: f32) Point {
return Point{ .x = x, .y = y };
}
pub fn distance(self: Point) f32 {
return @sqrt(self.x * self.x + self.y * self.y);
}
};
学習ポイント:
-
structで構造体定義 - メソッドは構造体内の関数
-
selfは明示的な引数 - デフォルト値をサポート
10. defer と errdefer
defer の基本
const std = @import("std");
pub fn main() !void {
std.debug.print("Start\n", .{});
defer std.debug.print("End\n", .{});
std.debug.print("Middle\n", .{});
// 出力順序:
// Start
// Middle
// End
}
リソース管理
const std = @import("std");
fn processFile(allocator: std.mem.Allocator, path: []const u8) !void {
// メモリ確保
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer); // スコープ終了時に解放
std.debug.print("Processing {s}\n", .{path});
// エラーが起きても defer は実行される
if (std.mem.eql(u8, path, "bad.txt")) {
return error.BadFile;
}
// 正常終了時も defer は実行される
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
processFile(allocator, "good.txt") catch |err| {
std.debug.print("Error: {}\n", .{err});
};
}
errdefer(エラー時のみ実行)
const std = @import("std");
fn allocateAndProcess(allocator: std.mem.Allocator) ![]u8 {
const buffer = try allocator.alloc(u8, 1024);
errdefer allocator.free(buffer); // エラー時のみ解放
// 何か処理
if (false) return error.ProcessingFailed;
// 成功時は buffer を返す(解放しない)
return buffer;
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
const buffer = try allocateAndProcess(allocator);
defer allocator.free(buffer);
std.debug.print("Success\n", .{});
}
複数の defer
pub fn main() void {
defer std.debug.print("1\n", .{});
defer std.debug.print("2\n", .{});
defer std.debug.print("3\n", .{});
std.debug.print("Start\n", .{});
// 出力順序(LIFO):
// Start
// 3
// 2
// 1
}
Rustとの比較:
// Rust: Drop トレイト(暗黙的)
{
let _file = File::open("file.txt")?;
// スコープ終了時に自動でクローズ
}
// Zig: defer(明示的)
{
const file = try std.fs.cwd().openFile("file.txt", .{});
defer file.close(); // 明示的に遅延実行
}
学習ポイント:
-
deferはスコープ終了時に実行 -
errdeferはエラー時のみ実行 - LIFO順(後入れ先出し)
- Rustの Drop より明示的
11. comptime入門
comptime変数
pub fn main() void {
// コンパイル時に計算
comptime var x = 0;
inline for (0..5) |i| {
x += i;
}
std.debug.print("Sum: {}\n", .{x}); // 10
}
ジェネリック関数
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
pub fn main() void {
const x = max(i32, 10, 20);
const y = max(f32, 1.5, 2.5);
std.debug.print("max(10, 20) = {}\n", .{x});
std.debug.print("max(1.5, 2.5) = {}\n", .{y});
}
学習ポイント:
-
comptimeでコンパイル時計算 - ジェネリクスは
comptimeで実現 - 詳細はProfessional編で
12. 組み込み関数(基礎)
@as(型変換)
pub fn main() void {
const x: i32 = 10;
const y = @as(f32, @floatFromInt(x));
std.debug.print("x = {}, y = {}\n", .{x, y});
}
@intCast(整数キャスト)
pub fn main() void {
const x: i32 = 1000;
const y: i16 = @intCast(x);
std.debug.print("y = {}\n", .{y});
}
@TypeOf(型取得)
pub fn main() void {
const x = 10;
const y = 3.14;
std.debug.print("Type of x: {}\n", .{@TypeOf(x)});
std.debug.print("Type of y: {}\n", .{@TypeOf(y)});
}
学習ポイント:
-
@で始まる組み込み関数 -
@asで明示的な型変換 - 詳細はドキュメント参照
13. テスト
test ブロック
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
test "basic addition" {
const result = add(2, 3);
try std.testing.expectEqual(5, result);
}
test "negative numbers" {
const result = add(-5, 3);
try std.testing.expectEqual(-2, result);
}
// zig test でテスト実行
エラーのテスト
const MathError = error{DivisionByZero};
fn divide(a: i32, b: i32) MathError!i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
test "division by zero" {
const result = divide(10, 0);
try std.testing.expectError(error.DivisionByZero, result);
}
test "normal division" {
const result = try divide(10, 2);
try std.testing.expectEqual(5, result);
}
テストの実行
# 単一ファイルのテスト
zig test main.zig
# プロジェクト全体のテスト
zig build test
Rustとの比較:
// Rust
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
// Zig
test "add" {
try std.testing.expectEqual(5, add(2, 3));
}
学習ポイント:
-
testブロックでテスト -
std.testingでアサーション -
zig testで実行
14. まとめ
Beginner編で学んだこと
✅ Zigの基礎
- 変数宣言(var, const, comptime)
- 基本的な型
- 関数定義
- 制御構文
✅ エラーハンドリング
- エラーユニオン(!T)
- try と catch
- Rustの Result との比較
✅ Optional型
- ?T による null 安全性
- orelse と .?
- Rustの Option との比較
✅ リソース管理
- defer と errdefer
- 明示的なクリーンアップ
✅ 構造体とテスト
- struct の定義
- メソッド
- test ブロック
Rust vs Zig の主要な違い
| 概念 | Rust | Zig |
|---|---|---|
| 哲学 | 所有権システム | 明示的制御 |
| メモリ管理 | 暗黙的(Drop) | 明示的(defer) |
| エラー | Result | !T |
| null | Option | ?T |
| ジェネリクス | トレイト | comptime |
| メタプログラミング | マクロ | comptime |
| 隠れた制御フロー | 少ない | なし |
Zigの設計原則(再確認)
1. No hidden control flow
// Zigでは全てが明示的
const x = try divide(10, 0); // エラーが明示的
defer allocator.free(buffer); // 解放が明示的
2. Explicit allocators
// アロケータを常に渡す
const buffer = try allocator.alloc(u8, 1024);
3. Compile-time code execution
// comptimeで強力なメタプログラミング
comptime const size = calculateSize();
簡単なCLIツールの例
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const stdin = std.io.getStdIn().reader();
try stdout.print("Enter your name: ", .{});
var buffer: [100]u8 = undefined;
const input = try stdin.readUntilDelimiterOrEof(&buffer, '\n');
if (input) |name| {
const trimmed = std.mem.trim(u8, name, &std.ascii.whitespace);
try stdout.print("Hello, {s}!\n", .{trimmed});
}
}
次のステップ: Intermediate編へ
Beginner編を修了したあなたは:
- ✅ Zigの基本構文を理解している
- ✅ エラーハンドリングを使える
- ✅ 簡単なプログラムを書ける
Intermediate編では:
- 🚀 エラー処理の深掘り(エラーセットの組み合わせ、カスタムエラー)
- 🚀 メモリアロケータの概念と使い方
- 🚀 comptime の実践的な使い方
- 🚀 ユニオンとタグ付きユニオン
- 🚀 パッケージ構成とモジュール
- 🚀 標準ライブラリの活用
公式リソース: