詳細な概要
機能ハイライト
小さく、シンプルな言語
プログラミング言語の知識をデバッグするよりも、アプリケーションのデバッグに重点を置いてください。
Zigの全文法は、580行のPEG文法ファイルで指定されています。
隠された制御フローも、隠されたメモリ確保も、プリプロセッサも、マクロも存在しないのです。もしZigのコードが関数を呼び出すために飛び出しているように見えないなら、それは違います。つまり、次のコードはfoo()
とbar()
のみを呼び出し、何も型を知らなくてもこれが保証されることを意味します:
var a = b + c.d;
foo();
bar();
隠された制御フローの例:
- Dには
@property
関数があります。これは、フィールドアクセスのように見えるメソッドで、上記の例ではc.d
が関数を呼び出すかもしれません。 - C++、D、Rustには演算子のオーバーローディングがあるので、
+
演算子は関数を呼び出すかもしれません。 - C++、D、Goにはthrow/catch 例外機能があるので、
foo()
は例外をスローしてbar()
が呼ばれないようにするかもしれません。
Zigは、すべての制御フローを言語キーワードと関数呼び出しのみで管理することで、コードの保守性と可読性を高めています。
性能と安全性:2つの選択
Zigには4つのビルドモードがあり、これらはすべてスコープ粒度に至るまで混在させることが可能です。
パラメータ | Debug | ReleaseSafe | ReleaseFast | ReleaseSmall |
---|---|---|---|---|
最適化 - 速度の向上、デバッグの悪影響、コンパイル時の悪影響 | -O3 | -O3 | -Os | |
ランタイムセーフティチェック - 危険速度、危険サイズ、未定義動作の代わりのクラッシュ | On | On |
以下は、ビルドモードに関係なく、コンパイル時の整数オーバーフローの様子です:
test "integer overflow at compile time" {
const x: u8 = 255;
_ = x + 1;
}
$ zig test 1-integer-overflow.zig
assets/zig-code/features/1-integer-overflow.zig:3:11: error: overflow of integer type 'u8' with value '256'
以下は、安全性を確認したビルドにおける、実行時の様子です:
test "integer overflow at runtime" {
var x: u8 = 255;
x += 1;
}
$ zig test 2-integer-overflow-runtime.zig
1/1 2-integer-overflow-runtime.test.integer overflow at runtime...thread 3030394 panic: integer overflow
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/2-integer-overflow-runtime.zig:3:7: 0x103cc2e in test.integer overflow at runtime (test)
x += 1;
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/compiler/test_runner.zig:157:25: 0x1047f99 in mainTerminal (test)
if (test_fn.func()) |_| {
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/compiler/test_runner.zig:37:28: 0x103e01b in main (test)
return mainTerminal();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x103d159 in posixCallMainAndExit (test)
root.main();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x103ccc1 in _start (test)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/.zig-cache/o/9e3bfcb661c1ce3251fa44aa9886f390/test
これらは、freestandingを含むすべてのターゲットでスタックトレースが動作します。
Zigでは、安全性を有効にしたビルドモードに依存し、パフォーマンスのボトルネックとなる部分の安全性を選択的に無効化することができます。例えば、前の例はこのように修正することができます:
test "actually undefined behavior" {
@setRuntimeSafety(false);
var x: u8 = 255;
x += 1; // XXX undefined behavior!
}
Zigは、未定義の動作を、バグ対策とパフォーマンス向上の両面で、剃刀のように鋭い道具として使っています。
パフォーマンスについて言えば、ZigはCよりも速いです。
- リファレンス実装では、最先端の最適化のためのバックエンドとしてLLVMを使用しています。
- 他のプロジェクトが「リンクタイム最適化」と呼ぶものを、Zigは自動的に行うのです。
- ネイティブターゲットでは、クロスコンパイルは第一級の使用例のおかげで、高度なCPU機能が有効になります(-march=native)。
- 未定義の挙動を慎重に選択する。例えば、Zigでは符号付き整数も符号なし整数もオーバーフロー時の挙動が未定義であるのに対し、C言語では符号付き整数だけです。これにより、C言語ではできない最適化が容易になります。
- ZigはSIMDベクトル型を直接公開しており、移植性の高いベクトル化コードを簡単に記述することができます。
Zigは完全な安全言語ではありませんのでご注意ください。Zigの安全性に関するストーリーを追いたい方は、これらの号をご購読ください:
ZigはC言語に依存するのではなく、C言語と競争する
Zig標準ライブラリはlibcと統合されていますが、libcに依存しているわけではありません。以下はHello Worldです:
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe 4-hello.zig
$ ./4-hello
Hello, world!
-O ReleaseSmall
、デバッグシンボル除去、シングルスレッドでコンパイルすると、x86_64-linuxターゲットで9.8 KiBの静的実行ファイルが作成されます:
$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded
$ wc -c hello
9944 hello
$ ldd hello
not a dynamic executable
Windowsでのビルドはさらに小さく、4096バイトになります:
$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded -target x86_64-windows
$ wc -c hello.exe
4096 hello.exe
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
順序に依存しないトップレベル宣言
グローバル変数のようなトップレベルの宣言は、順序に依存せず、遅延的に解析される。グローバル変数の初期化値はコンパイル時に評価されます。
var y: i32 = add(10, x);
const x: i32 = add(12, 34);
test "global variables" {
assert(x == 46);
assert(y == 56);
}
fn add(a: i32, b: i32) i32 {
return a + b;
}
const std = @import("std");
const assert = std.debug.assert;
$ zig test 5-global-variables.zig
1/1 5-global-variables.test.global variables...OK
All 1 tests passed.
nullポインタの代わりにオプショナル型
他のプログラミング言語では、null参照は多くの実行時例外の原因となり、コンピュータサイエンスの最悪の過ちとまで非難されています。
飾り気のないZigポインタはnullにできません:
test "null @intToPtr" {
const foo: *i32 = @ptrFromInt(0x0);
_ = foo;
}
$ zig test 6-null-to-ptr.zig
assets/zig-code/features/6-null-to-ptr.zig:2:35: error: pointer type '*i32' does not allow address zero
しかし、どのような型でもその前に?を付けることによってオプショナル型にすることができます:
const std = @import("std");
const assert = std.debug.assert;
test "null @intToPtr" {
const ptr: ?*i32 = @ptrFromInt(0x0);
assert(ptr == null);
}
$ zig test 7-optional-syntax.zig
1/1 7-optional-syntax.test.null @intToPtr...OK
All 1 tests passed.
オプションの値をアンラップするには、orelse
を使ってデフォルト値を指定することができます:
// malloc prototype included for reference
extern fn malloc(size: size_t) ?*u8;
fn doAThing() ?*Foo {
const ptr = malloc(1234) orelse return null;
// ...
}
また、ifを使うという選択肢もあります:
fn doAThing(optional_foo: ?*Foo) void {
// do some stuff
if (optional_foo) |foo| {
doSomethingWithFoo(foo);
}
// do some stuff
}
whileでも同じ構文が使えます:
const std = @import("std");
pub fn main() void {
const msg = "hello this is dog";
var it = std.mem.tokenize(u8, msg, " ");
while (it.next()) |item| {
std.debug.print("{s}\n", .{item});
}
}
$ zig build-exe 10-optional-while.zig
$ ./10-optional-while
hello
this
is
dog
手動のメモリ管理
Zigで書かれたライブラリは、どこでも使用することができます:
- デスクトップアプリケーション
- 低レイテンシーサーバ
- OSカーネル
- 組込み機器
- リアルタイムソフトウェア(例:ライブ、飛行機、ペースメーカーなど)
- WebAssemblyを搭載したWebブラウザやその他プラグイン
- CのABIを使用するその他プログラミング言語呼び出し
これらを実現するために、Zigのプログラマは自分自身のメモリを管理しなければならず、メモリ割り当ての失敗を処理しなければなりません。
これは、Zig標準ライブラリにも当てはまります。メモリを確保する必要がある関数はすべてアロケータパラメータを受け取ります。その結果、Zig標準ライブラリは、独立したターゲットに対しても使用することができます。
Zigではエラー処理を見直すに加えて、defer とerrdeferを提供して、メモリに限らずあらゆるリソース管理をシンプルかつ容易に検証できるようにしています。
defer
の例については、FFI/バインディングを使わないCライブラリとの統合を参照してください。ここでは、errdefer
を使用した例を示します:
const Device = struct {
name: []u8,
fn create(allocator: *Allocator, id: u32) !Device {
const device = try allocator.create(Device);
errdefer allocator.destroy(device);
device.name = try std.fmt.allocPrint(allocator, "Device(id={d})", id);
errdefer allocator.free(device.name);
if (id == 0) return error.ReservedDeviceId;
return device;
}
};
エラー処理を見直す
エラーは値であり、無視できない場合があります:
const std = @import("std");
pub fn main() void {
_ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
}
$ zig build-exe 12-errors-as-values.zig
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/12-errors-as-values.zig:4:30: error: error union is discarded
_ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/12-errors-as-values.zig:4:30: note: consider using 'try', 'catch', or 'if'
referenced by:
callMain: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:17
callMainWithArgs: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:12
remaining reference traces hidden; use '-freference-trace' to see all reference traces
エラーはcatchで処理することができます:
const std = @import("std");
pub fn main() void {
const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch |err| label: {
std.debug.print("unable to open file: {}\n", .{err});
const stderr = std.io.getStdErr();
break :label stderr;
};
file.writeAll("all your codebase are belong to us\n") catch return;
}
$ zig build-exe 13-errors-catch.zig
$ ./13-errors-catch
unable to open file: error.FileNotFound
all your codebase are belong to us
キーワードtryはcatch |err| return err
のショートカットです:
const std = @import("std");
pub fn main() !void {
const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
defer file.close();
try file.writeAll("all your codebase are belong to us\n");
}
$ zig build-exe 14-errors-try.zig
$ ./14-errors-try
error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/posix.zig:1768:23: 0x1066a70 in openatZ (14-errors-try)
.NOENT => return error.FileNotFound,
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:880:16: 0x1038294 in openFileZ (14-errors-try)
const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:827:5: 0x1034d9e in openFile (14-errors-try)
return self.openFileZ(&path_c, flags);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/14-errors-try.zig:4:18: 0x1034be8 in main (14-errors-try)
const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
^
これはスタックトレースではなくエラーリターントレースであることに注意しましょう。コードはそのトレースを得るためにスタックを巻き戻すという代償を支払わなかったのです。
エラー時に使用されるswitchキーワードは、起こりうるすべてのエラーを確実に処理するものです:
const std = @import("std");
test "switch on error" {
_ = parseInt("hi", 10) catch |err| switch (err) {};
}
fn parseInt(buf: []const u8, radix: u8) !u64 {
var x: u64 = 0;
for (buf) |c| {
const digit = try charToDigit(c);
if (digit >= radix) {
return error.DigitExceedsRadix;
}
x = try std.math.mul(u64, x, radix);
x = try std.math.add(u64, x, digit);
}
return x;
}
fn charToDigit(c: u8) !u8 {
const value = switch (c) {
'0'...'9' => c - '0',
'A'...'Z' => c - 'A' + 10,
'a'...'z' => c - 'a' + 10,
else => return error.InvalidCharacter,
};
return value;
}
$ zig build-exe 15-errors-switch.zig
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:509:45: error: root struct of file '15-errors-switch' has no member named 'main'
switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
~~~~^~~~~
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/15-errors-switch.zig:1:1: note: struct declared here
const std = @import("std");
^~~~~
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:20: note: called from here
return callMain();
~~~~~~~~^~
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:438:36: note: called from here
std.posix.exit(callMainWithArgs(argc, argv, envp));
~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
referenced by:
_start: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:351:40
remaining reference traces hidden; use '-freference-trace' to see all reference traces
キーワードunreachableは、エラーが発生しないことを表明するために使用されます:
const std = @import("std");
pub fn main() void {
const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
file.writeAll("all your codebase are belong to us\n") catch unreachable;
}
$ zig build-exe 16-unreachable.zig
$ ./16-unreachable
thread 3030318 panic: attempt to unwrap error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/posix.zig:1768:23: 0x10699b0 in openatZ (16-unreachable)
.NOENT => return error.FileNotFound,
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:880:16: 0x1039a94 in openFileZ (16-unreachable)
const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:827:5: 0x103723e in openFile (16-unreachable)
return self.openFileZ(&path_c, flags);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/16-unreachable.zig:4:77: 0x1034fcf in main (16-unreachable)
const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x10347d9 in posixCallMainAndExit (16-unreachable)
root.main();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x1034341 in _start (16-unreachable)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)
安全でないビルドモードではundefined behaviorを呼び出すので、成功が保証されている場合にのみ使用するようにしてください。
すべてのターゲットのスタックトレース
このページで紹介するスタックトレースとエラーリターントレースは、すべてのTier 1サポート及びいくつかのTier 2サポートターゲットで機能します。自立型も!
さらに、標準ライブラリには、任意の時点でスタックトレースをキャプチャし、後で標準エラーにダンプする機能があります:
const std = @import("std");
var address_buffer: [8]usize = undefined;
var trace1 = std.builtin.StackTrace{
.instruction_addresses = address_buffer[0..4],
.index = 0,
};
var trace2 = std.builtin.StackTrace{
.instruction_addresses = address_buffer[4..],
.index = 0,
};
pub fn main() void {
foo();
bar();
std.debug.print("first one:\n", .{});
std.debug.dumpStackTrace(trace1);
std.debug.print("\n\nsecond one:\n", .{});
std.debug.dumpStackTrace(trace2);
}
fn foo() void {
std.debug.captureStackTrace(null, &trace1);
}
fn bar() void {
std.debug.captureStackTrace(null, &trace2);
}
$ zig build-exe 17-stack-traces.zig
$ ./17-stack-traces
first one:
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:365:29: 0x10396e7 in captureStackTrace (17-stack-traces)
addr.* = it.next() orelse {
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:26:32: 0x103717c in foo (17-stack-traces)
std.debug.captureStackTrace(null, &trace1);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:16:8: 0x10350c8 in main (17-stack-traces)
foo();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x1034979 in posixCallMainAndExit (17-stack-traces)
root.main();
^
second one:
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:365:29: 0x10396e7 in captureStackTrace (17-stack-traces)
addr.* = it.next() orelse {
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:30:32: 0x103719c in bar (17-stack-traces)
std.debug.captureStackTrace(null, &trace2);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:17:8: 0x10350cd in main (17-stack-traces)
bar();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x1034979 in posixCallMainAndExit (17-stack-traces)
root.main();
^
この手法は、現在進行中のGeneralPurposeDebugAllocatorプロジェクトで使用されているのを見ることができます。
ジェネリックなデータ構造と関数
型とは、コンパイル時に知っておかなければならない値です:
const std = @import("std");
const assert = std.debug.assert;
test "types are values" {
const T1 = u8;
const T2 = bool;
assert(T1 != T2);
const x: T2 = true;
assert(x);
}
$ zig test 18-types.zig
1/1 18-types.test.types are values...OK
All 1 tests passed.
ジェナリックなデータ構造は、単にtype
を返す関数です:
const std = @import("std");
fn List(comptime T: type) type {
return struct {
items: []T,
len: usize,
};
}
pub fn main() void {
var buffer: [10]i32 = undefined;
var list = List(i32){
.items = &buffer,
.len = 0,
};
list.items[0] = 1234;
list.len += 1;
std.debug.print("{d}\n", .{list.items.len});
}
$ zig build-exe 19-generics.zig
$ ./19-generics
10
コンパイル時反映とコンパイル時コード実行
@typeInfoビルトイン関数でリフレクションを実現します:
const std = @import("std");
const Header = struct {
magic: u32,
name: []const u8,
};
pub fn main() void {
printInfoAboutStruct(Header);
}
fn printInfoAboutStruct(comptime T: type) void {
const info = @typeInfo(T);
inline for (info.Struct.fields) |field| {
std.debug.print(
"{s} has a field called {s} with type {s}\n",
.{
@typeName(T),
field.name,
@typeName(field.type),
},
);
}
}
$ zig build-exe 20-reflection.zig
$ ./20-reflection
20-reflection.Header has a field called magic with type u32
20-reflection.Header has a field called name with type []const u8
Zig標準ライブラリは、この手法でフォーマットprintを実装しています。小さくシンプルな言語であるにもかかわらず、ZigのフォーマットprintはすべてZigで実装されているのです。一方、C言語では、printfのコンパイルエラーはコンパイラにハードコードされています。同様に、Rustでは、整形印刷マクロはコンパイラにハードコーディングされています。
Zigは関数やコードのブロックをコンパイル時に評価することもできます。グローバル変数の初期化など、いくつかのコンテキストでは、式は暗黙のうちにコンパイル時に評価されます。そうでなければ、comptimeというキーワードを使って、明示的にコンパイル時にコードを評価することができます。これは、アサーションと組み合わせたときに特に強力になります:
const std = @import("std");
const assert = std.debug.assert;
fn fibonacci(x: u32) u32 {
if (x <= 1) return x;
return fibonacci(x - 1) + fibonacci(x - 2);
}
test "compile-time evaluation" {
var array: [fibonacci(6)]i32 = undefined;
@memset(&array, 42);
comptime {
assert(array.len == 12345);
}
}
$ zig test 21-comptime.zig
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:412:14: error: reached unreachable code
assets/zig-code/features/21-comptime.zig:15:15: note: called from here
FFI/バインディングを使用しないCライブラリとの統合
@cImportは、型、変数、関数、簡単なマクロを直接インポートして、Zigで使用することができます。また、インライン関数をC言語からZigに変換することもできます。
libsoundio使って正弦波を出す例です:
const c = @cImport(@cInclude("soundio/soundio.h"));
const std = @import("std");
fn sio_err(err: c_int) !void {
switch (err) {
c.SoundIoErrorNone => {},
c.SoundIoErrorNoMem => return error.NoMem,
c.SoundIoErrorInitAudioBackend => return error.InitAudioBackend,
c.SoundIoErrorSystemResources => return error.SystemResources,
c.SoundIoErrorOpeningDevice => return error.OpeningDevice,
c.SoundIoErrorNoSuchDevice => return error.NoSuchDevice,
c.SoundIoErrorInvalid => return error.Invalid,
c.SoundIoErrorBackendUnavailable => return error.BackendUnavailable,
c.SoundIoErrorStreaming => return error.Streaming,
c.SoundIoErrorIncompatibleDevice => return error.IncompatibleDevice,
c.SoundIoErrorNoSuchClient => return error.NoSuchClient,
c.SoundIoErrorIncompatibleBackend => return error.IncompatibleBackend,
c.SoundIoErrorBackendDisconnected => return error.BackendDisconnected,
c.SoundIoErrorInterrupted => return error.Interrupted,
c.SoundIoErrorUnderflow => return error.Underflow,
c.SoundIoErrorEncodingString => return error.EncodingString,
else => return error.Unknown,
}
}
var seconds_offset: f32 = 0;
fn write_callback(
maybe_outstream: ?[*]c.SoundIoOutStream,
frame_count_min: c_int,
frame_count_max: c_int,
) callconv(.C) void {
_ = frame_count_min;
const outstream: *c.SoundIoOutStream = &maybe_outstream.?[0];
const layout = &outstream.layout;
const float_sample_rate: f32 = @floatFromInt(outstream.sample_rate);
const seconds_per_frame = 1.0 / float_sample_rate;
var frames_left = frame_count_max;
while (frames_left > 0) {
var frame_count = frames_left;
var areas: [*]c.SoundIoChannelArea = undefined;
sio_err(c.soundio_outstream_begin_write(
maybe_outstream,
@ptrCast(&areas),
&frame_count,
)) catch |err| std.debug.panic("write failed: {s}", .{@errorName(err)});
if (frame_count == 0) break;
const pitch = 440.0;
const radians_per_second = pitch * 2.0 * std.math.pi;
var frame: c_int = 0;
while (frame < frame_count) : (frame += 1) {
const float_frame: f32 = @floatFromInt(frame);
const sample = std.math.sin((seconds_offset + float_frame *
seconds_per_frame) * radians_per_second);
{
var channel: usize = 0;
while (channel < @as(usize, @intCast(layout.channel_count))) : (channel += 1) {
const channel_ptr = areas[channel].ptr;
const sample_ptr: *f32 = @alignCast(@ptrCast(&channel_ptr[@intCast(areas[channel].step * frame)]));
sample_ptr.* = sample;
}
}
}
const float_frame_count: f32 = @floatFromInt(frame_count);
seconds_offset += seconds_per_frame * float_frame_count;
sio_err(c.soundio_outstream_end_write(maybe_outstream)) catch |err| std.debug.panic("end write failed: {s}", .{@errorName(err)});
frames_left -= frame_count;
}
}
pub fn main() !void {
const soundio = c.soundio_create();
defer c.soundio_destroy(soundio);
try sio_err(c.soundio_connect(soundio));
c.soundio_flush_events(soundio);
const default_output_index = c.soundio_default_output_device_index(soundio);
if (default_output_index < 0) return error.NoOutputDeviceFound;
const device = c.soundio_get_output_device(soundio, default_output_index) orelse return error.OutOfMemory;
defer c.soundio_device_unref(device);
std.debug.print("Output device: {s}\n", .{device.*.name});
const outstream = c.soundio_outstream_create(device) orelse return error.OutOfMemory;
defer c.soundio_outstream_destroy(outstream);
outstream.*.format = c.SoundIoFormatFloat32NE;
outstream.*.write_callback = write_callback;
try sio_err(c.soundio_outstream_open(outstream));
try sio_err(c.soundio_outstream_start(outstream));
while (true) c.soundio_wait_events(soundio);
}
$ zig build-exe sine.zig -lsoundio -lc
$ ./sine
Output device: Built-in Audio Analog Stereo
^C
このZigのコードは、同等のC言語のコードよりもかなりシンプルですまた、より多くの安全保護機能を備えており、これらはすべてCのヘッダーファイルを直接インポートすることで実現されています(APIバインディングなし)。
Zigは、C言語がC言語のライブラリを使うより、Cのライブラリを使う方が得意です。
ZigはC言語コンパイラでもある
ここでは、ZigがC言語のコードを構築する例を紹介します:
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello world\n");
return 0;
}
$ zig build-exe hello.c --library c
$ ./hello
Hello world
このとき実行されたC言語コンパイラのコマンドは--verbose-cc
で確認することができます:
$ zig build-exe hello.c --library c --verbose-cc
zig cc -MD -MV -MF .zig-cache/tmp/42zL6fBH8fSo-hello.o.d -nostdinc -fno-spell-checking -isystem /home/andy/dev/zig/build/lib/zig/include -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-gnu -isystem /home/andy/dev/zig/build/lib/zig/libc/include/generic-glibc -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-any -isystem /home/andy/dev/zig/build/lib/zig/libc/include/any-linux-any -march=native -g -fstack-protector-strong --param ssp-buffer-size=4 -fno-omit-frame-pointer -o .zig-cache/tmp/42zL6fBH8fSo-hello.o -c hello.c -fPIC
なお、もう一度コマンドを実行すると、出力はなく、即座に終了します:
$ time zig build-exe hello.c --library c --verbose-cc
real 0m0.027s
user 0m0.018s
sys 0m0.009s
これは、Build Artifact Cachingのおかげです。Zigは.dファイルを自動的に解析し、作業の重複を避けるために堅牢なキャッシュシステムを使用します。
ZigはC言語コードをコンパイルできるだけでなく、ZigをC言語コンパイラとして使用する非常に良い理由があります:Zigはlibcと一緒に出荷されています.
C言語コードに依存する関数、変数、型のエクスポート
Zigの主な使用例の1つは、他のプログラミング言語から呼び出せるように、C言語のABIを持つライブラリをエクスポートすることです。関数、変数、型の前にexport
キーワードを付けると、それらがライブラリAPIの一部となります:
export fn add(a: i32, b: i32) i32 {
return a + b;
}
静的ライブラリを作成するには:
$ zig build-lib mathtest.zig
シェアードライブラリを作成するには:
$ zig build-lib mathtest.zig -dynamic
ここでは、Zig Build Systemを使った例を紹介します:
#include "mathtest.h"
#include <stdio.h>
int main(int argc, char **argv) {
int32_t result = add(42, 1337);
printf("%d\n", result);
return 0;
}
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));
const exe = b.addExecutable("test", null);
exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
exe.linkLibrary(lib);
exe.linkSystemLibrary("c");
b.default_step.dependOn(&exe.step);
const run_cmd = exe.run();
const test_step = b.step("test", "Test the program");
test_step.dependOn(&run_cmd.step);
}
$ zig build test
1379
クロスコンパイルは第一級のユースケース
Zigはサポート表(see latest release notes)にあるどのターゲットに対しても、Tier3サポート以上の条件でビルドすることが可能です。「クロスツールチェーン」をインストールしたりする必要はありません。以下は、ネイティブなHello Worldです:
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe 4-hello.zig
$ ./4-hello
Hello, world!
あとは、x86_64-windows, x86_64-macos, aarch64-linux用にビルドします:
$ zig build-exe hello.zig -target x86_64-windows
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
$ zig build-exe hello.zig -target x86_64-macos
$ file hello
hello: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ zig build-exe hello.zig -target aarch64-linux
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped
Tier 3以上のターゲットであれば、どのようなターゲットにも有効である。
Zigにはlibcが付属しています
利用可能なlibcのターゲットはzig targets
で見つけることができます:
...
"libc": [
"aarch64_be-linux-gnu",
"aarch64_be-linux-musl",
"aarch64_be-windows-gnu",
"aarch64-linux-gnu",
"aarch64-linux-musl",
"aarch64-windows-gnu",
"armeb-linux-gnueabi",
"armeb-linux-gnueabihf",
"armeb-linux-musleabi",
"armeb-linux-musleabihf",
"armeb-windows-gnu",
"arm-linux-gnueabi",
"arm-linux-gnueabihf",
"arm-linux-musleabi",
"arm-linux-musleabihf",
"arm-windows-gnu",
"mips64el-linux-gnuabi64",
"mips64el-linux-gnuabin32",
"mips64el-linux-musl",
"mips64-linux-gnuabi64",
"mips64-linux-gnuabin32",
"mips64-linux-musl",
"mipsel-linux-gnu",
"mipsel-linux-musl",
"mips-linux-gnu",
"mips-linux-musl",
"powerpc64le-linux-gnu",
"powerpc64le-linux-musl",
"powerpc64-linux-gnu",
"powerpc64-linux-musl",
"powerpc-linux-gnu",
"powerpc-linux-musl",
"riscv64-linux-gnu",
"riscv64-linux-musl",
"s390x-linux-gnu",
"s390x-linux-musl",
"sparc-linux-gnu",
"sparcv9-linux-gnu",
"wasm32-freestanding-musl",
"x86-linux-gnu",
"x86-linux-musl",
"x86-windows-gnu",
"x86_64-linux-gnu",
"x86_64-linux-gnux32",
"x86_64-linux-musl",
"x86_64-windows-gnu"
],
このことは、これらのターゲットの--library c
はどのシステムファイルにも依存しないということを意味します!
もう一度、Cのhello worldの例を見てみましょう:
$ zig build-exe hello.c --library c
$ ./hello
Hello world
$ ldd ./hello
linux-vdso.so.1 (0x00007ffd03dc9000)
libc.so.6 => /lib/libc.so.6 (0x00007fc4b62be000)
libm.so.6 => /lib/libm.so.6 (0x00007fc4b5f29000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00007fc4b5d0a000)
libdl.so.2 => /lib/libdl.so.2 (0x00007fc4b5b06000)
librt.so.1 => /lib/librt.so.1 (0x00007fc4b58fe000)
/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc4b6672000)
glibcは静的ビルドをサポートしていませんが、muslはサポートしています:
$ zig build-exe hello.c --library c -target x86_64-linux-musl
$ ./hello
Hello world
$ ldd hello
not a dynamic executable
この例では、Zigはmusl libcをソースからビルドし、それに対してリンクしています。x86_64-linux用のmusl libcのビルドはcaching systemのおかげで利用可能なままなので、このlibcが再び必要になったときには即座に利用可能な状態になります。
つまり、この機能はどのプラットフォームでも利用できるのです。WindowsとmacOSのユーザーは、上記のどのターゲットに対しても、ZigとCのコードをビルドし、libcに対してリンクすることができます。同様に、他のアーキテクチャ用にクロスコンパイルすることも可能です:
$ zig build-exe hello.c --library c -target aarch64-linux-gnu
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped
ある意味、ZigはCコンパイラーよりも優れたCコンパイラーです!
この機能は、Zigと一緒にクロスコンパイルツールチェーンをバンドルしている以上のものです。例えば、Zigが出荷するlibcのヘッダの総サイズは、非圧縮で22MiBです。一方、x86_64のmusl libc + linux headersだけで8MiB、glibcでは3.1MiB(glibcにはlinux headersがない)ですが、Zigには現在40のlibcsが同梱されています。素朴なバンドルでは、444MiBになります。しかし、このprocess_headers toolと、いくつかのgood old manual laborのおかげで、Zigのバイナリtarballは、これらすべてのターゲットに対してlibcをサポートし、さらにcompiler-rt、libunwind、libcxx、そしてclang互換のC言語コンパイラにもかかわらず、およそ30 MiBのままになっています。比較のために、llvm.orgからclang 8.0.0自体のWindowsバイナリビルドは、132MiBです。
なお、Tier 1 Supportターゲットのみが徹底的にテストされています。今後、libcsの追加(Windows用も含む)及びすべてのlibcsに対してビルドするためのテストカバレッジの追加が予定されています。
Zigパッケージマネージャを搭載する予定ですがまだできていません。そのひとつは、Cライブラリのパッケージを作ることです。これによって、ZigビルドシステムZigプログラマにとってもCプログラマにとっても魅力的なものになるはずです。
Zigビルドシステム
Zigにはビルドシステムが付属しているので、makeやcmakeなどは必要ありません。
$ zig init-exe
Created build.zig
Created src/main.zig
Next, try `zig build --help` or `zig build run`
const std = @import("std");
pub fn main() anyerror!void {
std.debug.print("All your base are belong to us.\n");
}
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("example", "src/main.zig");
exe.setBuildMode(mode);
const run_cmd = exe.run();
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
b.default_step.dependOn(&exe.step);
b.installArtifact(exe);
}
その--help
メニューを見てみましょう。
$ zig build --help
Usage: zig build [steps] [options]
Steps:
install (default) Copy build artifacts to prefix path
uninstall Remove build artifacts from prefix path
run Run the app
General Options:
--help Print this help and exit
--verbose Print commands before executing them
--prefix [path] Override default install prefix
--search-prefix [path] Add a path to look for binaries, libraries, headers
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for.
-Drelease-safe=[bool] optimizations on and safety on
-Drelease-fast=[bool] optimizations on and safety off
-Drelease-small=[bool] size optimizations on and safety off
Advanced Options:
--build-file [file] Override path to build.zig
--cache-dir [path] Override path to zig cache directory
--override-lib-dir [arg] Override path to Zig lib directory
--verbose-tokenize Enable compiler debug output for tokenization
--verbose-ast Enable compiler debug output for parsing into an AST
--verbose-link Enable compiler debug output for linking
--verbose-ir Enable compiler debug output for Zig IR
--verbose-llvm-ir Enable compiler debug output for LLVM IR
--verbose-cimport Enable compiler debug output for C imports
--verbose-cc Enable compiler debug output for C compilation
--verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
利用可能なステップの1つが実行されていることがわかります。
$ zig build run
All your base are belong to us.
以下は、ビルドスクリプトの例です:
非同期関数による並行処理
Zig 0.5.0非同期関数の導入。この機能はホストOSに依存せず、ヒープで確保されたメモリにも依存しない。つまり、自立したターゲットで非同期関数が利用できるのです。
Zigは関数が非同期かどうかを推測し、非同期でない関数ではasync
/await
を許可します。これは、Zigライブラリはブロッキングと非同期I/Oの区別がつかないということを意味します。Zigは関数カラーを避ける.
Zig標準ライブラリは、非同期関数をスレッドプールに多重化するイベントループを実装し、M:N並列を実現しています。マルチスレッドの安全性と競合検出は、現在活発に研究されている分野です。
幅広いターゲットへの対応
Zigは、ターゲットごとにサポートレベルを伝える「サポートティア」制度を採用しています。
パッケージメンテナへのフレンドリーな対応
リファレンスのZigコンパイラはまだ完全にセルフホスティングされていませんが、何があっても、システムC++コンパイラを持つことから、どんなターゲットに対しても完全にセルフホスティングされたZigコンパイラを持つことまでは、ちょうど3ステップで済みます。Maya Rashishが指摘するように、Zigの他のプラットフォームへの移植は楽しくてスピーディなのです。
非デバッグビルドモードは再現性/決定性があります。
JSON版ダウンロードページがあります。
Zigチームには、パッケージの保守を経験したメンバーが何人もいます。
- DaurnimatorはArch Linuxパッケージを保守しています。
- Marc Tiehuisは、Visual Studio Codeパッケージを管理しています。
- Andrew Kelleyは、1年ほどかけてDebianとUbuntuのパッケージングを行い、さりげなくnixpkgsにコントリビュートしています。
- Jeff FowlerはHomebrewパッケージを保守し、Sublimeパッケージを開始しました(現在はemekoiが保守しています)。