Zigプログラミング言語入門:C言語の後継を目指す新星
シンプルさと制御性を追求するシステムプログラミング言語Zigの特徴、基本文法、そしてCとの相互運用性を実践的に解説します。
Zigプログラミング言語入門:C言語の後継を目指す新星
Zigは2016年にAndrew Kelleyが開発を始めたシステムプログラミング言語です。「C言語よりもシンプルで、より安全なシステムプログラミング」を目標に設計されており、近年その注目度が急速に高まっています。
Zigが目指すもの
Zigの設計哲学は明快です:
- 隠れた制御フローなし:コードを読んだままに動作する
- 隠れたメモリ確保なし:アロケータを明示的に渡す
- プリプロセッサなし:マクロの代わりにコンパイル時コード実行
- 未定義動作なし:すべての動作が定義されている
Rustとの違いを一言で表すなら、「Rustは安全性を強制するが、ZigはCのような制御性を保ちながらより予測可能にする」です。
インストールと最初のプログラム
# macOS
brew install zig
# バージョン確認
zig version
// hello.zig
const std = @import("std");
pub fn main() void {
std.debug.print("こんにちは、Zig!\n", .{});
}
zig run hello.zig
基本的な型と変数
const std = @import("std");
pub fn main() void {
// 定数(compileエラー:変更不可)
const x: i32 = 42;
// 変数(mutableが明示的)
var y: f64 = 3.14;
y += 1.0;
// 型推論
const z = "Hello"; // []const u8
// 整数型: i8, i16, i32, i64, i128, u8, u16, u32, u64, u128
// 浮動小数: f16, f32, f64, f128
// アーキテクチャ依存: isize, usize
std.debug.print("x={}, y={d:.2}, z={s}\n", .{ x, y, z });
}
エラーハンドリング
Zigのエラー処理は非常に明確です。エラーユニオン型を使います。
const std = @import("std");
fn divide(a: f64, b: f64) !f64 {
if (b == 0.0) return error.DivisionByZero;
return a / b;
}
pub fn main() !void {
const result = divide(10.0, 2.0) catch |err| {
std.debug.print("エラー: {}\n", .{err});
return;
};
std.debug.print("結果: {d}\n", .{result}); // 5.0
// tryキーワードで伝播
const result2 = try divide(10.0, 0.0);
_ = result2;
}
!Tは「エラーまたはT型の値」を表すエラーユニオン型です。
メモリ管理:アロケータパターン
Zigにはガベージコレクタがありません。アロケータを明示的に渡すことでメモリを管理します。
const std = @import("std");
pub fn main() !void {
// General Purpose Allocator(開発用:メモリリーク検出)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// 動的配列(ArrayList)
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
try list.append(1);
try list.append(2);
try list.append(3);
for (list.items) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
deferはスコープ終了時に実行される文です。リソース解放に非常に便利です。
コンパイル時コード実行(comptime)
Zigの最大の特徴の一つがcomptimeです。マクロを使わずに、通常のZigコードをコンパイル時に実行できます。
const std = @import("std");
fn makeArray(comptime T: type, comptime size: usize) [size]T {
var arr: [size]T = undefined;
for (&arr, 0..) |*item, i| {
item.* = @intCast(i);
}
return arr;
}
pub fn main() void {
const arr = makeArray(i32, 5);
std.debug.print("{any}\n", .{arr}); // { 0, 1, 2, 3, 4 }
}
これにより、テンプレートメタプログラミングのような機能を、はるかに読みやすい形で実現できます。
C言語との相互運用
ZigはCとの相互運用が非常に優れています。CのコードをZigから直接呼び出せます。
const c = @cImport({
@cInclude("stdio.h");
@cInclude("string.h");
});
pub fn main() void {
_ = c.printf("CからZigへ: %s\n", "こんにちは");
var buf: [256]u8 = undefined;
_ = c.snprintf(&buf, buf.len, "数値: %d", 42);
_ = c.printf("%s\n", &buf);
}
また、ZigのコードをCライブラリとしてコンパイルすることも可能です。
// add.zig
export fn add(a: i32, b: i32) i32 {
return a + b;
}
zig build-lib add.zig -dynamic
ビルドシステム
Zigには優れた組み込みビルドシステムがあります。
// 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 = "my-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
const run_step = b.step("run", "アプリを実行");
run_step.dependOn(&run_cmd.step);
}
zig build run
zig build -Doptimize=ReleaseFast # 最適化ビルド
クロスコンパイル
Zigのクロスコンパイル対応は業界最高レベルです。
# LinuxのARMバイナリをmacOSでビルド
zig build -Dtarget=aarch64-linux-musl
# WindowsのEXEをLinuxでビルド
zig build -Dtarget=x86_64-windows
追加のツールチェーンなしでこれができるのは驚異的です。
ZigとWASM
ZigはWASMターゲットもサポートしています。
zig build-exe src/main.zig -target wasm32-freestanding -O ReleaseSmall
// wasm向けのエクスポート関数
export fn add(a: i32, b: i32) i32 {
return a + b;
}
Rustとの比較
| 特徴 | Zig | Rust |
|---|---|---|
| 学習曲線 | 比較的緩やか | 急峻 |
| 安全性強制 | 任意 | 強制 |
| Cとの相互運用 | ネイティブ | FFI経由 |
| コンパイル時間 | 高速 | 遅め |
| エコシステム | 発展中 | 成熟 |
| comptime | ◎ | macro(複雑) |
まとめ
Zigは「C言語の精神的後継」として、シンプルさ・制御性・予測可能性を重視しています。特に:
- C言語プロジェクトの段階的置き換え
- 組み込みシステム開発
- 高性能WASMモジュール
- クロスプラットフォームツール
で威力を発揮します。まだバージョン1.0に達していませんが、Bun(JavaScriptランタイム)などの著名プロジェクトでの採用が進んでおり、今後の動向から目が離せない言語です。