本書は、プログラミング言語Zigのチュートリアルです。
Zigは、堅牢で最適かつ再利用可能なソフトウェアを維持するための汎用システムプログラミング言語およびツールチェインです{{Cite web
|url=https://ziglang.org/
|title=Home ⚡ Zig Programming Language
|website=ziglang.org
|accessdate=2022-07-17
|cite=Zig is a general-purpose programming language and toolchain for maintaining robust, optimal and reusable software.
。
Zigは、アンドリュー・ケリー( ''[https://andrewkelley.me/ Andrew Kelley]'' )によって設計され、静的で強い型付けで型推論とジェネリックプログラミングをサポートします。
__TOC__
Zigは、2016年2月に発表された比較的若いプログラミング言語で{{Cite web
|last1=Kelley
|first1=Andrew
|title=Introduction to the Zig Programming Language
|url=https://andrewkelley.me/post/intro-to-zig.html
|website=andrewkelley.me
|accessdate=2022-07-17
、2024年2月10日現在の最新バージョンは 0.11.0 で、''pre-release'' と位置づけられています{{Cite web
|url=https://github.com/ziglang/zig/releases
|title=Releases · ziglang/zig · GitHub
|website=github.com
|date=2023-08-04
|accessdate=2024-02-10
。このため Hello world ですら、バージョン間で互換性がなくなることもあり、今後もリリースバージョンまでは言語仕様やライブラリーおよびツールチェインの仕様が変更される可能性があります。
Zigはメモリ安全性、高速なビルド、そして明快な言語設計を特長とする新しいプログラミング言語です。
以下のZigのクイックツアーでは、基本的な概念とコード例を紹介します。
基本構文:
Zigプログラムはmain関数から始まります。ここでは、stdout.writeAll()関数を使って標準出力に文字列を出力しています。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    try stdout.writeAll("Hello, World!\n");  
}
Hello, world!
 
データ型:
Zigには整数型、浮動小数点型、真偽値などの基本的なデータ型があります。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const age: i32 = 25;
    const salary: f64 = 50000.50;
    const isZigFun: bool = true;
    const message: []const u8 = "Hello, Zig!";
    const intOrNull: ?i16 = 42;
    const nullOrInt: ?i16 = null;
    const intOrError: anyerror!i16 = 178;
    const errorOrInt: anyerror!i16 = error.BadValue;
    
    try stdout.print("age = {}({})\n", .{age, @TypeOf(age)});
    try stdout.print("salary = {}({})\n", .{salary, @TypeOf(salary)});
    try stdout.print("isZigFun = {}({})\n", .{isZigFun, @TypeOf(isZigFun)});
    try stdout.print("message = {s}({})\n", .{message, @TypeOf(message)});
    try stdout.print("intOrNull = {?}({})\n", .{intOrNull, @TypeOf(intOrNull)});
    try stdout.print("nullOrInt = {?}({})\n", .{nullOrInt, @TypeOf(nullOrInt)});
    try stdout.print("intOrError = {!}({})\n", .{intOrError, @TypeOf(intOrError)});
    try stdout.print("errorOrInt = {!}({})\n", .{errorOrInt, @TypeOf(errorOrInt)}); 
}
age = 25(i32)
salary = 5.00005e+04(f64)
isZigFun = true(bool)
message = Hello, Zig!([]const u8)
intOrNull = 42(?i16)
nullOrInt = null(?i16)
intOrError = 178(anyerror!i16)
errorOrInt = error.BadValue(anyerror!i16)
 
制御構造:
if、else if、else文を使って条件分岐ができます。whileループやforループを使って繰返し処理ができます。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const zero: f16 = 0.0;
    const num = zero/zero;
    if (num > 0.0) {
        try stdout.writeAll("Positive\n");
    } else if (num < 0.0) {
        try stdout.writeAll("Negative\n");
    } else if (num == 0.0) {
        try stdout.writeAll("Zero\n");
    } else {
        try stdout.print("num = {}\n", .{num});
    }
    
    var a: ?u16 = 42;
    if (a) |*value| {
        try stdout.print("value.* = {}({})\n", .{value.*, @TypeOf(value.*)});
        value.* = 123;
    } else {
        unreachable;
    }
    if (a) |n| {
        try stdout.print("n = {}({})\n", .{n, @TypeOf(n)});
    } else {
        unreachable;
    }
    a = null;
    if (a) |_| {
        unreachable;
    } else {
        try stdout.print("a = {?}({})\n", .{a, @TypeOf(a)});
    }
    
    var b: anyerror!u32 = error.BadValue;
    try stdout.print("b = {any}({})\n", .{b, @TypeOf(b)});
    if (b) |_| {
        unreachable;
    } else |err| {
        try stdout.print("err = {?}({})\n", .{err, @TypeOf(err)});
    }
    b = 4423;
    try stdout.print("b = {any}({})\n", .{b, @TypeOf(b)});
    if (b) |n| {
        try stdout.print("n = {}({})\n", .{n, @TypeOf(n)});
    } else |_| {
        unreachable;
    }
    
    var x: i32 = 0;
    while (x < 4) : (x += 1) {
        try stdout.print("While: Iteration {}\n", .{x});
    }
    for (0..4) |i| {
        try stdout.print("For: Iteration {}\n", .{i});
    }
}
num = -nan
value.* = 42(u16)
n = 123(u16)
a = null(?u16)
b = error.BadValue(anyerror!u32)
err = error.BadValue(anyerror)
b = 4423(anyerror!u32)
n = 4423(u32)
While: Iteration 0
While: Iteration 1
While: Iteration 2
While: Iteration 3
For: Iteration 0
For: Iteration 1
For: Iteration 2
For: Iteration 3
 
関数:
関数はfnキーワードを使って宣言します。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}
pub fn main() !void {
    const result = add(5, 3);
    try stdout.print("Sum: {}\n", .{result});
}
Sum: 8
 
構造体とメソッド:
構造体はstructキーワードを使って定義します。メソッドは構造体の関数を定義することで実現します。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const myCar = Car.init("Toyota", 2022);
    try stdout.print("myCar = {}\n", .{myCar});
}
const Car = struct {
    model: []const u8,
    year: u32,
    const Self = @This();
    pub fn init(model: []const u8, year: u32) Self {
        return Self{
            .model = model,
            .year = year,
        };
    }
    pub fn format(
        car: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        try out_stream.print("Car(Model: {s}, Year: {})", .{ car.model, car.year });
    }
};
myCar = Car(Model: Toyota, Year: 2022)
 
 
ここでは、Zigの基本的な構文とコンセプトを簡単に紹介しました。
[https://ziglang.org/documentation/master/ Zig Language Reference]の、Hello worldの変遷（新しい順）。
master@2024-02-10{{Cite web
|url=https://ziglang.org/documentation/master/#Hello-World
|title=Zig Documentation(master@2024-02-10)
|website=ziglang.org
|accessdate=2024-02-10
0.11.0{{Cite web
|url=https://ziglang.org/documentation/0.11.0/#Hello-World
|title=Zig Documentation(0.11.0)
|website=ziglang.org
|accessdate=2024-02-10
0.10.0{{Cite web
|url=https://ziglang.org/documentation/0.10.0/#Hello-World
|title=Zig Documentation(0.10.0)
|website=ziglang.org
|accessdate=2022-11-15
0.9.1{{Cite web
|url=https://ziglang.org/documentation/0.9.1/#Hello-World
|title=Zig Documentation(0.9.1)
|website=ziglang.org
|accessdate=2022-07-17
[https://wandbox.org/permlink/dghWOQlzCD7jSCHj 0.8.1]{{Cite web
|url=https://ziglang.org/documentation/0.8.1/#Hello-World
|title=Zig Documentation(0.8.1)
|website=ziglang.org
|accessdate=2022-07-17
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {s}!\n", .{"world"});
}
|url=https://ziglang.org/download/0.8.0/release-notes.html#Formatted-Printing
|title=0.8.0 Release Notes
|website=ziglang.org
|accessdate=2022-07-17
|quote=Disable the special casing of {} for u8 slices/arrays. Unless {s} is specified the contents won't be treated as a string.
0.7.1{{Cite web
|url=https://ziglang.org/documentation/0.7.1/#Hello-World
|title=Zig Documentation(0.7.1)
|website=ziglang.org
|accessdate=2022-07-17
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {}!\n", .{"world"});
}
0.6.0{{Cite web
|url=https://ziglang.org/documentation/0.6.0/#Hello-World
|title=Zig Documentation(0.6.0)
|website=ziglang.org
|accessdate=2022-07-17
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().outStream();
    try stdout.print("Hello, {}!\n", .{"world"});
}
初期化の初期値から try がなくなった。
0.4.0{{Cite web
|url=https://ziglang.org/documentation/0.4.0/#Hello-World
|title=Zig Documentation(0.4.0)
|website=ziglang.org
|accessdate=2022-07-17
0.5.0{{Cite web
|url=https://ziglang.org/documentation/0.5.0/#Hello-World
|title=Zig Documentation(0.5.0)
|website=ziglang.org
|accessdate=2022-07-17
const std = @import("std");
pub fn main() !void {
    // If this program is run without stdout attached, exit with an error.
    const stdout_file = try std.io.getStdOut();
    // If this program encounters pipe failure when printing to stdout, exit
    // with an error.
    try stdout_file.write("Hello, world!\n");
}
stdout_file が var から const に変更された。
0.3.0{{Cite web
|url=https://ziglang.org/documentation/0.3.0/#Hello-World
|title=Zig Documentation(0.3.0)
|website=ziglang.org
|accessdate=2022-07-17
0.2.0{{Cite web
|url=https://ziglang.org/documentation/0.2.0/#Hello-World
|title=Zig Documentation(0.2.0)
|website=ziglang.org
|accessdate=2022-07-17
const std = @import("std");
pub fn main() !void {
    // If this program is run without stdout attached, exit with an error.
    var stdout_file = try std.io.getStdOut();
    // If this program encounters pipe failure when printing to stdout, exit
    // with an error.
    try stdout_file.write("Hello, world!\n");
}
0.1.1{{Cite web
|url=https://ziglang.org/documentation/0.1.1/#hello-world
|title=Zig Documentation
|website=ziglang.org
|accessdate=2022-07-17
const io = @import("std").io;
pub fn main() -> %void {
    %%io.stdout.printf("Hello, world!\n");
}
Zigは、コンパイル時に安全で高速なシステムプログラミング言語です。以下に、Zigの主な特徴を詳しく説明します。
Zigは静的型付けを採用しています。型はコンパイル時に解決され、実行時に型エラーを引き起こすことがありません。また、型推論もサポートされています。
Zigは、値型を採用しています。これは、オブジェクトのコピーが作成されるため、値型はポインタ型よりも安全で、予測可能な動作をします。
Zigには、コンパイル時メタプログラミングをサポートするためのcomptimeブロックがあります。これにより、コンパイル時に計算を実行し、コンパイル時に情報を収集することができます。
Zigは、メモリ管理について堅牢で安全なアプローチを採用しています。メモリリークやダングリングポインタなどの問題を回避するために、コンパイル時にメモリ安全性を検査することができます。
Zigには、モジュールシステムがあります。モジュールは、依存関係を管理するための仕組みを提供します。
Zigは、ネイティブコードを生成するコンパイラを提供します。これにより、高速かつ効率的なコードを実行することができます。
Zigには、エラーハンドリング機能があります。エラーハンドリングは、C言語のエラーコードや例外処理のようなアプローチに比べて、より安全で予測可能な動作をすることができます。
以上が、Zigの主な特徴のいくつかです。これらの特徴により、Zigは高速かつ安全なシステムプログラミングを可能にします。
以下、個別の要素に言及します。
コンパイル型言語 : １つまたは複数のソースコードをコンパイルして実行ファイルを得、それを実行します。
静的型付け : 値・変数・関数のパラメーター・関数の戻値などの型はコンパイル時に検証され、型安全性が担保されます。
例外はありません : エラー処理などのための例外はありません。この用途には、関数の戻値にエラーユニオン型やオプショナル型を使います。
演算子オーバーロードはありません : 演算子は演算子一覧表にある通りにしか機能しませんし、必ずそのように機能します。シフト演算子がストリーム入出力を行ったりはしません。
関数オーバーロードはありません : 関数に与えられたパラメーターによって違う関数が呼び出されることはありません。ただし、可変引数の関数は定義できます。その場合も、同じ関数が呼び出されます。
型推論が行われます : 変数宣言時に与えられた初期値（必須）から変数の型を推論することで型アノテーションを省略できます。
ガベージコレクション : ガベージコレクションはありません。
クラスはありませんがメソッドはあります : クラスはありませんが、コンテナー( struct, union, enum )がメソッドを持つことができます。
コンストラクターはありません : 慣習的に init() と名付けられたメソッドがコンテナーのインスタンス化に使われます。
デストラクターはありません : 慣習的に deinit() と名付けられたメソッドがコンテナーのインスタンスの解体処理に使われます。deinit() はスコープでを抜ける時に自動的に実行されません。defer myObj.deinit(); のようにインスタンスの生成時に解体処理を登録します。
継承はありません : タグ付きunionやusingnamespaceを使うと継承でしたいこと（＝差分プログラミング）が可能ですが、構文や仕組みは異なります。
interface や protocol はありません : Java や Go の interface や Swift の protocol はありませんが、コンパイル時にメソッドが定義されているかテストし、なければエラーにするプログラミングはできます。
名前空間の役割はコンテナーが担います : namespace の様なキーワードはありませんが、ドットシンタックス（コンテナー.識別子 や コンテナー変数.識別子）でコンテナーが名前空間の役目を果たします。また、usingnamespace を使うとドットシンタックスなしに公開識別子にアクセスできるようになります（ミックスインとも言えます）。
ジェネリックプログラミングに対応しています : 関数呼出しの引数に（コンパイル時に既知の）型を渡すことができるので、ジェネリックプログラミングを行うことが出来ます。
ソースファイルは匿名struct : コンパイル単位であるソースファイルは暗黙のうちに匿名structで、組込み関数@import() が返す値を識別子に束縛することで名前空間を構成します。ex: const std = @import("std")
インスタンスがスコープを抜けるときに実行する処理を登録できます : スコープを抜ける理由によって、 defer と errdefer の2種類の処理を登録できます。
多くの制御構造は式と文の構文を持ちます : if, while, for の3つの制御構文は、値を返す式構文とブロックを持つ文構文の両方を持ちます。switchは、値を返す式構文しかありません。
文末の;（セミコロン）の省略はできません : 最近の新興言語にして珍しく ; は省略できません。
ループ構文は else 節を持つことができます : whileとfor の2つの反復構文は、ループを「完走」した時に実行する else 節を持つことができます。
キーワードを識別子にできます : PL/Iのように無制限にではなく @"else" のような形式でキーワードや数字で始まる識別子を使うことができます。
シャドウイング禁止 : 外部スコープで使われている識別子と同じ識別子を内側のスコープで使うことは出来ません。コンパイルエラーになります。
関数のパラメーターはイミュータブル : 関数のパラメーターは、 const 宣言された変数（＝定数）と同じく書換えることは出来ません。
実際にZigで書かれたのコードを見てみましょう。
Ruby#ユーザー定義クラスの都市間の大圏距離を求めるメソッドを追加した例を、Zigに移植しました。
都市間の大圏距離:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const GeoCoord = struct {
    longitude: f64,
    latitude: f64,
    const Self = @This();
    pub fn init(longitude: f64, latitude: f64) Self {
        return Self{
            .longitude = longitude,
            .latitude = latitude,
        };
    }
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        var ew = "東経";
        var ns = "北緯";
        var long = self.longitude;
        var lat = self.latitude;
        if (long < 0.0) {
            ew = "西経";
            long = -long;
        }
        if (lat < 0.0) {
            ns = "南緯";
            lat = -lat;
        }
        try out_stream.print("({s}: {}, {s}: {})", .{ ew, long, ns, lat });
    }
    pub fn distance(self: Self, other: Self) f64 {
        const math = std.math;
        const i = math.pi / 180.0;
        const r = 6371.008;
        return math.acos(math.sin(self.latitude * i) * math.sin(other.latitude * i) +
            math.cos(self.latitude * i) * math.cos(other.latitude * i) * math.cos(self.longitude * i - other.longitude * i)) * r;
    }
};
pub fn main() !void {
    const sites = comptime .{
        .{
            .name = "東京",
            .gc = GeoCoord.init(139.7673068, 35.6809591),
        },
        .{
            .name = "シドニー・オペラハウス",
            .gc = GeoCoord.init(151.215278, -33.856778),
        },
        .{
            .name = "グリニッジ天文台",
            .gc = GeoCoord.init(-0.0014, 51.4778),
        },
    };
    inline for (sites) |site| {
        try stdout.print("{s}: {}\n", .{ site.name, site.gc });
    }
    try stdout.writeAll("---\n");
    inline for (sites, 0..) |a, index| {
        const b = sites[(index + 1) % sites.len];
        try stdout.print("{s} - {s}: {} [km]\n", .{ a.name, b.name, a.gc.distance(b.gc) });
    }
}
実行結果:
東京: (東経: 1.397673068e+02, 北緯: 3.56809591e+01)
シドニー・オペラハウス: (東経: 1.51215278e+02, 南緯: 3.3856778e+01)
グリニッジ天文台: (西経: 1.4e-03, 北緯: 5.14778e+01)
---
東京 - シドニー・オペラハウス: 7.823269299386704e+03 [km]
シドニー・オペラハウス - グリニッジ天文台: 1.69872708377249e+04 [km]
グリニッジ天文台 - 東京: 9.560546566490015e+03 [km]
Zig言語では、特定の名前で定義された format メソッドは、標準ライブラリである std.fmt を拡張し、コレクションの固有の文字列化メソッドを提供します。Zigでは、配列内に文字列を含む配列は第一級オブジェクトではないため、ストリーム入出力を拡張する方法が安全性を考慮する上で確実です。
新しい機能として、0.11.0で導入された「multi-object for loops」があります。この新しい構文を使用すると、同じ長さの複数のシーケンスを反復処理することができます。これにより、コードがより読みやすくなり、メンテナンスが容易になります。
また、for の前にある inline キーワードは、コンパイル時にループを展開することを意味し、タプルの配列であることをコンパイラに伝えます。これにより、コンパイラがより効率的なコードを生成できます。
ファイルを開いて内容をアロケートしたバッファに読出し標準出力に出力する例。
ファイルの読出し:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const file = try std.fs.openFileAbsolute(
        "/etc/hosts",
        .{},
    );
    defer file.close();
    var reader = std.io.bufferedReader(file.reader());
    const in_stream = reader.reader();
    const allocator = std.heap.page_allocator;
    const file_size = try file.getEndPos();
    const contents = try in_stream.readAllAlloc(allocator, file_size);
    defer allocator.free(contents);
    try stdout.writeAll(contents);
}
このコードは、指定された絶対パスにあるファイル（この場合は"/etc/hosts"）を開いて、その内容を標準出力に書き込むプログラムです。以下に、コードの各部分の機能を解説します。
const std = @import("std");: 標準ライブラリをインポートします。
const stdout = std.io.getStdOut().writer();: 標準出力のライターを取得します。
pub fn main() !void { ... }: プログラムのエントリーポイントとなるmain関数です。エラーが発生する可能性があるため、!void型が使用されています。
const file = try std.fs.openFileAbsolute("/etc/hosts", .{});: /etc/hostsファイルを絶対パスとして開きます。tryキーワードは、関数がエラーを返す可能性があることを示します。
defer file.close();: ファイルの処理が終了した後に、必ずファイルを閉じるようにします。deferキーワードは、そのスコープが終了する際に処理を実行することを示します。
var reader = std.io.bufferedReader(file.reader());: ファイルから読み取りを行うためのBufferedReaderを作成します。
const in_stream = reader.reader();: BufferedReaderから読み取りストリームを取得します。
const allocator = std.heap.page_allocator;: ページアロケータを使用してメモリを割り当てるためのアロケータを定義します。
const file_size = try file.getEndPos();: ファイルのサイズを取得します。
const contents = try in_stream.readAllAlloc(allocator, file_size);: ファイルの内容を読み取り、アロケータを使用してメモリを割り当てます。
defer allocator.free(contents);: メモリを解放するため、contentsがスコープを抜けた時に必ず実行されるようにします。
try stdout.writeAll(contents);: ファイルの内容を標準出力に書き込みます。エラーが発生した場合には、そのエラーが処理されます。
このプログラムは、指定されたファイルの内容を読み取り、その内容を標準出力に書き込みます。
ジェネリックプログラミングを使った複素数型を実装してみました。
[https://wandbox.org/permlink/RLtBPnQwfCsUkwt7 ジェネリックな複素数型]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const signbit = std.math.signbit;
fn Complex(comptime T: type) type {
    return struct {
        real: T,
        imag: T,
        const Self = @This();
        pub fn init(real: T, imag: T) Self {
            return Self{ .real = real, .imag = imag };
        }
        pub fn add(self: Self, other: Self) Self {
            return Self{ .real = self.real + other.real, .imag = self.imag + other.imag };
        }
        pub fn format(
            self: Self,
            comptime fmt: []const u8,
            options: std.fmt.FormatOptions,
            out_stream: anytype,
        ) !void {
            try std.fmt.formatType(self.real, fmt, options, out_stream, 1);
            if (signbit(self.imag)) {
                try out_stream.writeAll("-");
                try std.fmt.formatType(-self.imag, fmt, options, out_stream, 1);
            } else {
                try out_stream.writeAll("+");
                try std.fmt.formatType(self.imag, fmt, options, out_stream, 1);
            }
            return out_stream.writeAll("i");
        }
    };
}
pub fn main() !void {
    const F64Complex = Complex(f64);
    var f64cplx1 = F64Complex.init(3.0, 4.0);
    const f64cplx2 = F64Complex.init(1.0, -4.0);
    try stdout.print("{}\n", .{f64cplx1});
    try stdout.print("{d:.1}\n", .{f64cplx2});
    try stdout.print("{x}\n", .{f64cplx1.add(f64cplx2)});
}
実行結果:
3.0e+00+4.0e+00i 1.0-4.0i 
0x1p2+0x0.0p0i
pub fn Complex(comptime T: type) typeは、要素型をパラメーターに複素数型を返します（複素数ではなく複素数型です）。
Zig には演算子オーバーライドがないのでメソッドとして定義します。
標準ライブラリーのソート機能の使用例。
ソートの例:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var ary = [_]i8{ 1, 4, 1, 4, 2, 1, 3, 5, 6 };
    try stdout.print("befor: {any}\n", .{ary});
    std.sort.insertion(i8, &ary, {}, struct {
        pub fn cmp(context: void, a: i8, b: i8) bool {
            return std.sort.asc(i8)(context, a, b);
        }
    }.cmp);
    try stdout.print("after: {any}\n", .{ary});
}
実行結果:
befor: { 1, 4, 1, 4, 2, 1, 3, 5, 6 }
after: { 1, 1, 1, 2, 3, 4, 4, 5, 6 }
std.sort.insertion()は、型・配列・context・比較関数をパラメーターに配列をソートします。
Zigには、ラムダ式がないので匿名structのメソッドを比較関数に使いました。
structの配列のあるフィールドをキーにソートしたい場合は、匿名structのメソッドを比較関数に使う手口が有効ですし、ソート対象のstructのコードに手を入れられるときは、structのメソッドにしても良いでしょう（ラムダ式ほしい）。
エラトステネスの篩を、若干 Zig らしく書いてみました。
エラトステネスの篩:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
fn Eratosthenes(comptime n: u16) !void {
    var sieve: [n + 1]bool = undefined;
    for (&sieve, 0..) |*e, i| {
        e.* = i >= 2;
    }
    for (sieve, 0..) |_, i| {
        if (!sieve[i]) {
            continue;
        }
        try stdout.print("{} ", .{i});
        var j = 2 * i;
        while (j <= n) : (j += i) {
            sieve[j] = false;
        }
    }
}
pub fn main() !void {
    try Eratosthenes(1000);
}
実行結果:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997 
このコードは、0.11.0以降でしか動きません。
Crystalに移植しました例を題材に、Zigにはない抽象クラスの代替のコード例を書いてみました。
[https://wandbox.org/permlink/fA9yzsrUly8oRYbR 抽象クラス（の代替）]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const Point = struct {
    x: i32 = 0,
    y: i32 = 0,
    const Self = @This();
    pub fn init(x: i32, y: i32) Self {
        return Self{ .x = x, .y = y };
    }
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        return out_stream.print("x:{}, y:{}", .{ self.x, self.y });
    }
    pub fn move(self: Self, dx: i32, dy: i32) Self {
        self.x += dx;
        self.y += dy;
        return self;
    }
};
const Shape = struct {
    location: Point = Point.init(0, 0),
    const Self = @This();
    pub fn init(x: i32, y: i32) Self {
        return Self{ .location = Point.init(x, y) };
    }
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        return out_stream.print("{}", .{self.location});
    }
    pub fn move(self: Self, dx: i32, dy: i32) Self {
        self.location.move(dx, dy);
        return self;
    }
};
const Square = struct {
    shape: Shape = Shape.init(0, 0),
    wh: i32 = 0,
    const Self = @This();
    pub fn init(x: i32, y: i32, wh: i32) Self {
        return Self{ .shape = Shape.init(x, y), .wh = wh };
    }
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        return out_stream.print("{}, wh:{}", .{ self.shape, self.wh });
    }
    pub fn move(self: Self, dx: i32, dy: i32) Self {
        self.shape.move(dx, dy);
        return self;
    }
    pub fn area(self: Self) i32 {
        return self.wh * self.wh;
    }
};
const Rectangle = struct {
    shape: Shape = Shape.init(0, 0),
    width: i32 = 0,
    height: i32 = 0,
    const Self = @This();
    pub fn init(x: i32, y: i32, width: i32, height: i32) Self {
        return Self{ .shape = Shape.init(x, y), .width = width, .height = height };
    }
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        return out_stream.print("{}, width:{}, height:{}", .{ self.shape, self.width, self.height });
    }
    pub fn move(self: Self, dx: i32, dy: i32) Self {
        self.shape.move(dx, dy);
        return self;
    }
    pub fn area(self: Self) i32 {
        return self.width * self.height;
    }
};
const Circle = struct {
    shape: Shape = Shape.init(0, 0),
    r: i32 = 0,
    const Self = @This();
    pub fn init(x: i32, y: i32, r: i32) Self {
        return Self{ .shape = Shape.init(x, y), .r = r };
    }
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        _ = options;
        _ = fmt;
        return out_stream.print("{}, r:{}", .{ self.shape, self.r });
    }
    pub fn move(self: Self, dx: i32, dy: i32) Self {
        self.shape.move(dx, dy);
        return self;
    }
    pub fn area(self: Self) f32 {
        return @as(f32, @floatFromInt(self.r * self.r)) * std.math.pi;
    }
};
pub fn main() !void {
    const ary = comptime .{ Point.init(3, 4), Shape.init(20, 24), Rectangle.init(10, 20, 32, 24) };
    inline for (ary) |x| {
        try stdout.print("{}({})\n", .{ @TypeOf(x), x });
    }
    try stdout.writeAll("\n");
    const shapes = comptime .{ Square.init(10, 12, 40), Rectangle.init(10, 20, 32, 24), Circle.init(10, 12, 20) };
    inline for (shapes) |shape| {
        try stdout.print("{}({}).area() = {}\n", .{ @TypeOf(shape), shape, shape.area() });
    }
}
実行結果:
Point(x:3, y:4)
Shape(x:20, y:24)
Rectangle(x:10, y:20, width:32, height:24)
Square(x:10, y:12, wh:40).area() = 1600
Rectangle(x:10, y:20, width:32, height:24).area() = 768 
Circle(x:10, y:12, r:20).area() = 1.25663708e+03
インスタンスの文字列化は、format() メソッドを定義することで std.fmt を拡張しています。
area() メソッドを持つインスタンスを要素とするタプルを comptime 修飾した上で、inline な for で回しました。
この方法は、コンパイル時に全てのインスタンスが area() メソッドを持つことを担保します。
ここでは各インスタンスの型が Shape を継承してるわけではなく専ら area() メソッドを持つことを拠り所にしています。
その意味で、interface や protocol と似ていますが、「共通する特徴の定義」はしていません。
Zigには範囲型がないので代替のコード例を書いてみました。
[https://wandbox.org/permlink/jKGPlIi7JAKyYzPF 範囲型（の代替）]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const Range = struct {
    s: i32,
    e: i32,
    const Self = @This();
    pub fn init(s: i32, e: i32) Self {
        return Self{ .s = s, .e = e };
    }
    pub fn next(self: *Self) ?i32 {
        return if (self.s > self.e) null else blk: {
            const n = self.s;
            self.s += 1;
            break :blk n;
        };
    }
};
pub fn main() !void {
    var r = Range.init(23, 42);
    while (r.next()) |i| {
        try stdout.print("{} ", .{i});
    }
}
実行結果:
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 
エントリーポイント
コメント:
// から行末までがコメントです。
// /* ... */ スタイルのコメントはありません。
変数宣言
リテラル
\\ 複数行に渡る場合は
\\ この様に
\\ \\ を前置することで
\\ 記述できます。
制御構造
if ( 条件式 ) {
    // ブロック
}
if ( 条件式 ) {
    // ブロック１ 
} else {
    // ブロック２
}
if ( オプショナル式 ) | 変数 | {
    // ブロック１
} else {
    // ブロック２
}
if ( エラーユニオン式 ) | 変数 | {
    // ブロック１
} else | err | {
    // ブロック２
}
switch ( 式 ) {
    値１ => 式１,
    値２ａ, 値２ｂ => | 変数２ | 式２, // 変数２にキャプチャー
    値３ａ => | *変数３ | 式３, // 変数３にポインターをキャプチャー
elase => 式ｘ
}
var i = 1;
const q = while (i < 10) : (i += 1) {
    if (i > 5) {
        break true;
    } 
} else false;
var i: usize = 0;
outer: while (i < 10) : (i += 1) {
    while (true) {
        continue :outer;
    } 
}
while (オプショナル式) | 変数 | { // null でなければ変数にキャプチャー
  // ブロック１
} else { // null だったとき
  // ブロック２
}
while (エラーユニオン式) | 変数 | { // エラーコードでなければ変数にキャプチャー
  // ブロック１
} else |err| { // エラーコードを err にキャプチャー
  // ブロック２
}
const items = [_]i32 { 4, 5, 3, 4, 0 };
const q = for (items) | item | {
    if (item == 5) {
        break true;
    } 
} else false;
outer: for ([_]i32 { 4, 5, 3, 4, 0 }) | x | {
    for ([_]i32 { 1, 3, 5, 7 }) | y | {
        if ( x == y ) {
            continue :outer;
        }
    }
}
コンテナー
struct型{
    .メンバー１ = 式１,
    .メンバー２ = 式２,
    .メンバーｎ = 式ｎ
}
.{
    式１,
    式２,
    式ｎ
}
関数
[TODO:書くべき項目を並べてみましたが、例えば「値と型」だけでも網羅的に書いていくとコンテンツの分量が爆発するのが目に見えているので、過剰になったらリファレンス篇に移動するなどの方法で、各節はコンパクトさを心がけたい]
Zigでは、関数 mainがエントリーポイントです。
nop.zig:
pub fn main() void {}
なにもしないプログラムはこの様になりますが、エラー集合型を返す可能性があるときは
pub fn main() !void {
   // エラー集合型を返す可能性がある処理
}
と戻値の型を void から !void に代えます。
Zigのコメントに関する基本的なポイントを要約します。
3つのコメントのタイプ: Zigでは、通常のコメント、ドキュメントコメント、トップレベルのドキュメントコメントの3つのタイプがサポートされています。通常のコメントは無視されますが、ドキュメントコメントとトップレベルのドキュメントコメントは、パッケージのドキュメントを生成するためにコンパイラによって使用されます。
通常のコメント: 通常のコメントは // で始まり、次の改行文字（LFバイト）までがコメントとして扱われます。
ドキュメントコメント: ドキュメントコメントは、/// で始まります。複数のドキュメントコメントが連続する場合、1つの複数行のドキュメントコメントとして統合されます。ドキュメントコメントは、それに続く要素を文書化します。
トップレベルのドキュメントコメント: トップレベルのドキュメントコメントは、//! で始まります。これは、現在のモジュールを文書化します。
ドキュメントコメントの位置: ドキュメントコメントは特定の場所にのみ許可されています。例えば、式の途中や非ドキュメントコメントの直前にドキュメントコメントがある場合、コンパイルエラーになります。
通常のコメントとの統合: 現在、パッケージのドキュメントを生成する際に、通常のコメントはドキュメントコメントと統合されます。
Zigのコメントシステムは、コードの文書化と理解を容易にするための強力なツールです。
Zigでは、// から行末までがコメントです。
C言語の /* … */ のスタイルの複数行に渡るコメントはありません。
これは、コードの各行を文脈に関係なくトークン化できるようにするためです。
[https://wandbox.org/permlink/kAZ4vQFlg89ROhe9 hello.zig]:
// hello.zig:
const std = @import("std"); // 先頭に @ が付く関数は組込み関数です
pub fn main() !void { // 次の行の try 演算子がエラー集合を返す可能性があるので !void が戻値型
   try std.io.getStdOut().writeAll("Hello, World!\n");
}
Zigでは、/// から始まるコメントは特別なコメントで、Docコメント( ''[https://ziglang.org/documentation/master/#Doc-comments Doc comments]'' )と呼ばれます。
Docコメントは特定の場所にしか許されません。式の途中や非Docコメントの直前など、予想外の場所にdocコメントがあると、コンパイルエラーになります。
[TODO:サンプルコードと整形結果]
Zigでは、//! から始まるコメントは特別なコメントで、トップレベルDocコメント( ''[https://ziglang.org/documentation/master/#Top-Level-Doc-Comments Top-Level Doc Comments]'' )と呼ばれます。
コンテナレベルのドキュメントのように、直後のドキュメントに属さないユーザードキュメントに、トップレベルDocコメントを使います。
[TODO:サンプルコードと整形結果]
DocコメントおよびトップレベルDocコメントは、コンパイル時に zig build-exe -femit-docs ソースファイル.zig の様に、-femit-docs をあたえると、 docs/ 以下にドキュメントが生成されます。
Zigの値と型に関する基本的なポイントを要約します。
値
整数: 整数は、i32、i64などのサイズに応じた型で表されます。算術演算やビット演算などがサポートされます。
浮動小数点数: 浮動小数点数は、f32、f64などのサイズに応じた型で表されます。浮動小数点数演算などがサポートされます。
真偽値: trueとfalseの2つの値を持つブール型があります。
オプション型: 値が存在するかどうかを示すオプション型があり、nullで初期化されます。
エラー統合: エラーコードまたは正常な値を保持するエラー統合型があります。
型
プリミティブ型: i32、f64、boolなどの基本的なデータ型があります。さらに、ポインターサイズの整数や特定のABIに対応する型もあります。
任意の型: void、anyerror、typeなど、特殊な目的の型があります。
文字列リテラルとUnicodeコードポイントリテラル: 文字列リテラルは定数のポインターであり、UTF-8でエンコードされた文字列を表します。Unicodeコードポイントリテラルは、UTF-8でエンコードされたUnicodeコードポイントを表します。
代入と変数
const: constキーワードを使用して、定数の値を変数に割り当てます。一度割り当てられた値は変更できません。
var: varキーワードを使用して、変数を宣言し、初期化して値を変更できるようにします。
undefined: undefinedは変数を初期化せずに残すために使用されます。これは、不正な値を意味し、バグの兆候です。undefinedは、任意の型に変換できますが、その後はその値が未定義であることを検出することはできません。
Zigの値と型のシステムは、明確で柔軟なプログラミングをサポートし、安全なコードの作成を促進します。
[TOD0:整数・浮動小数点数・bool・文字列・union・struct・enum・配列・ベクトル・スライス・ポインター・ゼロビットな型, 関連する組込み関数]
[https://wandbox.org/permlink/MVU7WLn9nYmcSFmq formatを伴うprintと値と型]:
pub fn main() !void {
    const stdout = @import("std").io.getStdOut().writer();
    try stdout.print(" (1) = {}\n", .{42});
    try stdout.print(" (2) = {}\n", .{0x17});
    try stdout.print(" (3) = {}\n", .{0o17});
    try stdout.print(" (4) = {}\n", .{0b0100101});
    try stdout.print(" (5) = {}\n", .{1e222});
    try stdout.print(" (6) = {}\n", .{3.1415926536});
    try stdout.print(" (7) = {}\n", .{'c'});
    try stdout.print(" (8) = {c}\n", .{'c'});
    try stdout.print(" (9) = {s}\n", .{"abcdef"});
    try stdout.print("(10) = {}, {}\n", .{ 111, 999 });
    try stdout.print("(11) = {1}, {0}\n", .{ 111, 999 });
    try stdout.print("(12) = {1s}, {0}\n", .{ 111, "abc" });
    try stdout.print("(13) = {0d}, {0b}, {0o}, {0x}, {0X}\n", .{ 123 });
}
実行結果:
 (1) = 42
 (2) = 23
 (3) = 15
 (4) = 37
 (5) = 1.0e+222
 (6) = 3.1415926536e+00
 (7) = 99
 (8) = c
 (9) = abcdef
(10) = 111, 999
(11) = 999, 111
(12) = abc, 111
(13) = 123, 1111011, 173, 7b, 7B
print()の前の、[https://ziglang.org/documentation/master/#try try]は単項演算子です。
try は、右の式のエラーユニオン式を評価します。もしエラーであれば、同じエラーで現在の関数から戻ます。そうでない場合は、式はラップされていない値になります。
エラーユニオン型( [https://ziglang.org/documentation/master/#Error-Union-TypeError Union Type Error Union Type] )を返す関数は、try単項演算子か[https://ziglang.org/documentation/master/#catch catch]二項演算子で、値とエラーを弁別する必要があります（tryあるいはcatchがないと、コンパイル時にエラーになります）。
An error occurred:
/tmp/playground726918707/play.zig:3:10: error: error is ignored. consider using `try`, `catch`, or `if`   stdout.print(" (1) = {}\n", .{42});
                                                                                                                ^
print()の様に、標準ライブラリーの format()を使う関数は、書式化文字列とタプル（匿名 struct ） .{ … } を引数にします。C言語のような、可変引数ではなくタプルを使うのでprintf() に代表される可変引数関数は利便性は高いですが、書式化文字列と引数の型不一致が生じると、スタックフレームを非可逆的に破壊します。これを未然に防ぐことは、コンパイル時の書式化文字列の解析と引数の型情報の照合で可能ですが、コンパイル時のメモリーと計算量（≒時間）の増大に直結します。、プレースホルダーがない場合でも、空のタプル.{} は必須です。
書式化文字列:通常の文字列ですが { と } で囲まれたプレスホルダーが、タプルの当該順位の値（を書式化した文字列）に置換わります。
 と二文字重ねます。
書式化文字列のプレースホルダーによって、参照と文字列化される値のタプルです。
2つ以上の値を渡す場合は、第二引数を .{ 1, 2, 3 } の様にカンマ区切りのタプルにします（ {} の前の . （点）を忘れがちですが、型の省略を意味し必須です）。
基本的に、左から順にプレスホルダーにタプルの値が左から与えられますが、{0} {1} の書式で参照する引数の順位を明示できます。
書式指定と併用する時は、 stdout.print("? = {1s}, {0}\n", .{ 111, "abc" }) の様に順位が先、書式指定文字が後になります。
この機能は言語（自然言語）によって異なる語順を吸収することに使えそうですが、fmtの第一引数は comptime 修飾子がついていて変数にはできません。
数値（整数と浮動小数点数）や文字リテラルと文字列リテラルがあり、整数はいくつかの異なる基数表現が、浮動小数点数は指数表現と小数表現があります。
文字と文字列は明確に異なり、リテラルでは ’A’ が文字（@TypeOf('A') ⇒ comptime_int）、 ”ABC” が文字列（@TypeOf("ABC") ⇒ *const [3:0]u8）です。
嫌な予感がした人の直感は正解です。Zig では、文字列は第一級オブジェクトではなく文字（u8）の配列で、関数から返すときはアロケーターと defer の連携などで記憶域の寿命と値の妥当性を「プログラマー」が担保する必要があります。また、GoのGCはありません。CrystalのASTを操作できるマクロもありませんし、Rustの所有権も、C++のスマートポインターもありません。
このことは、C言語なみのプログラマー任せのメモリー管理を文字列以外でも強いられることを意味していますが、zig（コマンド）や標準ライブラリーのソースコードを読むと、複数の型でアロケーターを使い分け、スタック上のインスタンス（のハードウェア起因のスコープ）を使い分けられることを実践で証明しているので、pre-releaseから、initial-releasまでの間に、安定化・定式化が図られることを期待します。
[TODO:[https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig master/lib/std/fmt.zig]を観て書いています。参照すべき標準ライブラリーのドキュメントを出来たら/仕様が安定したら見直し]
 は符号付き7ビット整数を意味します。この表現の整数型に許される最大ビット幅は65535です。
構文(EBNF):
STRINGLITERALSINGLE = "\"" string_char* "\"" skip
STRINGLITERAL = STRINGLITERALSINGLE
              | ( line_string                skip )+
ox80_oxBF = [#x80-#xBF]
oxF4 = '\xF4'
ox80_ox8F = [#x80-#x8F]
oxF1_oxF3 = [#xF1-#xF3]
oxF0 = '\xF0'
ox90_0xBF = [#x90-#xBF]
oxEE_oxEF = [#xEE-#xEF]
oxED = '\xED'
ox80_ox9F = [#x80-#x9F]
oxE1_oxEC = [#xE1-#xEC]
oxE0 = '\xE0'
oxA0_oxBF = [#xA0-#xBF]
oxC2_oxDF = [#xC2-#xDF]
(* From https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/ *)
mb_utf8_literal = oxF4      ox80_ox8F ox80_oxBF ox80_oxBF
                | oxF1_oxF3 ox80_oxBF ox80_oxBF ox80_oxBF
                | oxF0      ox90_0xBF ox80_oxBF ox80_oxBF
                | oxEE_oxEF ox80_oxBF ox80_oxBF
                | oxED      ox80_ox9F ox80_oxBF
                | oxE1_oxEC ox80_oxBF ox80_oxBF
                | oxE0      oxA0_oxBF ox80_oxBF
                | oxC2_oxDF ox80_oxBF
ascii_char_not_nl_slash_squote = [\000-\011\013-\046-\050-\133\135-\177]
char_escape = "\\x" hex hex
            | "\\u{" hex+ "}"
            | "\\" [nr\\t'"]
char_char = mb_utf8_literal
          | char_escape
          | ascii_char_not_nl_slash_squote
string_char = char_escape
            |  [^\\"\n]
line_string = ( "\\\\" [^\n]* [ \n]* )+
文字列リテラルは、ヌル終端バイト配列への定数型単一項目ポインターです。文字列リテラルの型は、長さとヌル終端であるという事実の両方をコード化しているため、スライスとヌル終端ポインターの両方に強制することが可能です。文字列リテラルを再参照すると配列に変換されます[https://ziglang.org/documentation/master/#String-Literals-and-Unicode-Code-Point-Literals String Literals and Unicode Code Point Literals]。
Zigにおける文字列のエンコーディングは、事実上UTF-8であると仮定されています。ZigのソースコードはUTF-8でエンコードされているので、ソースコードの文字列リテラル内に現れる非ASCIIバイトは、そのUTF-8の意味をZigのプログラム内の文字列の内容に引き継ぎ、コンパイラーがそのバイトを修正することはありません。ただし、UTF-8以外のバイトを文字列リテラルに埋め込むことは可能で、その場合は \xNN 記法を使用します。
Unicodeコードポイントリテラルの型は comptime_int で整数リテラルと同じです。すべてのエスケープシーケンスは、文字列リテラルと Unicodeコードポイントリテラルの両方において有効です。
他の多くのプログラミング言語では、Unicodeコードポイントリテラルは「文字リテラル」と呼ばれます。しかし、Unicode仕様の最近のバージョン（Unicode 13.0時点）では、「文字」の正確な技術的定義は存在しません。Zigでは、Unicodeコードポイントリテラルは、Unicodeのコードポイントの定義に対応します。
註：有効なUnicodeポイントの最大値は0x10ffffです。
マルチライン文字列リテラルは、エスケープが必要なく、複数の行にわたって記述することができます。マルチライン文字列リテラルを始めるには、\\ トークンを使用します。コメントと同様に、文字列リテラルは行の末尾まで続きます。行末は文字列リテラルに含まれません。ただし、次の行が \\ で始まる場合、改行が追加され、文字列リテラルが続きます。
[https://wandbox.org/permlink/TvQE1aV2QioiEnC3 マルチライン文字列リテラル]:
const stdout = @import("std").io.getStdOut().writer();
const message =
    \\文字列リテラルを、エスケープ記号がなく複数行にまたがって書くことができます。
    \\複数行の文字列リテラルを開始するには、\\ トークンを使用します。
    \\コメントと同じように、文字列リテラルは行末まで続きます。
    \\行の終わりは文字列リテラルに含まれません。
    \\ただし、次の行が \\ で始まる場合は、改行が追加され文字列リテラルが続行されます。
pub fn main() !void {
    try stdout.print("message = {s}", .{message});
}
実行結果:
message = 文字列リテラルを、エスケープ記号がなく複数行にまたがって書くことができます。
複数行の文字列リテラルを開始するには、\\ トークンを使用します。
コメントと同じように、文字列リテラルは行末まで続きます。
行の終わりは文字列リテラルに含まれません。
ただし、次の行が \\ で始まる場合は、改行が追加され文字列リテラルが続行されます
Zigにおける代入に関する説明の要旨は以下の通りです。
const キーワードを使用して、識別子に値を割り当てます。
const で定義された識別子は変更できません。その値は定数です。
変数として値を変更できるようにするには、var キーワードを使用します。
変数は初期化される必要があります。初期化されていない変数を使用すると、コンパイルエラーが発生します。
初期化されていない変数には undefined を使用して、未定義のままにすることができます。
定数式や単純型の型推論には comptime を使用します。
具体的なコード例は以下の通りです。
 
const print = @import("std").debug.print;
// constを使って定数を定義
const x = 1234;
pub fn main() void {
    // 関数内でのconstは定数を定義
    const y = 5678;
    // 定数には値を再割り当てできない
    // y = 1;  // コンパイルエラー
    // varを使って変数を定義
    var z: i32 = 5678;
    // 変数に値を再割り当て
    z += 1;
    // 変数は初期化する必要がある
    // var w: i32;
    comptime var w = 1; // 初期化を伴った宣言は型推論される
    // 変数を undefined で初期化
    var u: i32 = undefined;
    u = 8;
    print("{d}\n", .{x}); // 1234
    print("{d}\n", .{y}); // 5678
    print("{d}\n", .{z}); // 5679
    print("{d}\n", .{w}); // 1
    print("{d}\n", .{u}); // 8
}
上記のコードを実行すると、それぞれの変数や定数の挙動を確認できます。
comptimeは、Zig言語においてコンパイル時に式が評価されることを示します。この概念は、ジェネリック、定数式の評価、コンパイル時の最適化、およびコンパイル時の静的解析において重要な役割を果たします。
以下に、comptimeの詳細を説明します：
fn example(comptime T: type) T {
    // Tの特定の型に基づいた処理を行う
}
const comptime MAX_VALUE: usize = 100;
const arraySize = comptime 10;
const comptime result = someFunction();
comptime if (someCondition) {
    // このブロックはコンパイル時に評価される
} else {
    // このブロックもコンパイル時に評価される
}
comptimeの使用により、Zig言語ではコンパイル時に高度な静的解析が実行され、パフォーマンスの向上やコードの安全性が確保されます。
このコードはコンパイルエラーになります。
[https://wandbox.org/permlink/0Wy4ptgSN9Rrxnq7 定数の初期値が関数の戻値だとエラー]:
fn mul(x: usize, y: usize) usize {
    return x * y;
}
pub fn main() void {
    const len : usize = mul(3, 4);
    const ary: [len]i32 = undefined;
    _ = ary;
}
コンパイル結果:
An error occurred:
playground/playground3656639701/play.zig:7:17: error: unable to resolve comptime value
playground/playground3656639701/play.zig:7:17: note: array length must be comptime-known
エラーメッセージの意味は以下の通りです：
error: unable to resolve comptime value: コンパイル時に解決できない値があります。
note: array length must be comptime-known: 配列の長さはコンパイル時に既知である必要があります。つまり、配列の長さは実行時に決定されることは許されません。
つまり、Zigでは配列の長さなどの定数値はコンパイル時に解決できる必要があります。そのため、関数の戻り値のように実行時に決定される値を定数の初期値として使用することはできません。
C++であれば、constexpr が適用なケースですが、Zigでは次のような解決方法を取ります。下記コードはエラーになりません。
[https://wandbox.org/permlink/bAZkdzqajcuNpEOG comptimeを追加しコンパイル時に実行]:
fn mul(x: usize, y: usize) usize {
    return x * y;
}
pub fn main() void {
    const len : usize = comptime mul(3, 4); // mul の前に comptime を追加
    const ary: [len]i32 = undefined;
    _ = ary;
}
変更点は mul() の呼出しを [https://ziglang.org/documentation/master/#comptime comptime] で修飾しただけです。comptime は、修飾子式をコンパイル時に実行する修飾子で、式の中でコンパイル時に未定な値が参照されると、エラーとなります。ここでは、数リテラル同士の商を求めているので、コンパイル時値が確定できます。
_ = ary は、「未使用変数」をサプレッスするときのイディオムです。
をサポートしています。
Zigのテストフレームワークは、テストを実行し、アサーションを評価し、カバレッジレポートを生成するための機能を提供します。
以下は、Zigのテストに関する概要です。
テスト宣言: テストは、testキーワードに続いて名前（オプション）とテストの本体を含むブロックで構成されます。これにより、コードの特定の部分や関数が期待どおりに動作するかを検証できます。
テスト実行: zig testコマンドを使用してテストを実行します。このコマンドは、テスト用のビルドを作成し、デフォルトのテストランナーを実行します。テストランナーは、テスト宣言を見つけて実行し、その結果を出力します。
テスト結果の報告: テストランナーは、テスト結果を標準エラーに出力します。成功したテスト、失敗したテスト、スキップされたテストなどの情報が報告されます。これにより、開発者はテストの状態を把握し、問題がある場合は修正できます。
テストのスキップ: error.SkipZigTestを返すことでテストをスキップすることができます。また、zig testコマンドに--test-filterオプションを指定して、特定のテストのみを実行することもできます。
テストの自動化: テストはコードの一部として記述されるため、変更があるたびに手動で実行する必要がありません。CI/CDパイプラインなどの自動化ツールと統合して、コードの品質を継続的に確認できます。
Zigのテストフレームワークは、コードの信頼性を向上させ、開発プロセスを効率化するのに役立ちます。テストは、バグを早期に発見し、コードの安定性を確保するのに不可欠な要素です。
テスト宣言の構文(EBNF):
test-decl = [ doc_comment ] "test" [ STRINGLITERALSINGLE ] block
if-test.zig:
const std = @import("std");
const expectEqual = std.testing.expectEqual;
test "if expr" {
    const f = true;
    var x: usize = 5;
    x += if (f) 10 else 20;
    try expectEqual(x, 15);
}
test "if stmt" {
    const f = true;
    var x: isize = 10;
    if (!f) {
        x += 10;
    } else {
        x -= 20;
    }
    try expectEqual(x, -10);
}
コマンドライン:
% zig test if-test.zig
All 2 tests passed.
このZigのテストコードは、 if 式と if 文の振る舞いをテストしています。
"if expr": if 式を使用して、条件に応じて異なる値を返し、その結果を変数に代入しています。その後、変数の値が期待通りであることを確認します。
"if stmt": if 文を使用して、条件に応じて異なるステートメントを実行し、その結果を変数に代入しています。その後、変数の値が期待通りであることを確認します。
テスト結果は、両方のテストが成功し、期待通りの結果が得られたことを示しています。つまり、条件式が正しく評価され、それに基づいて適切な操作が行われました。
このように、Zigのテストフレームワークを使用することで、さまざまな条件下でのプログラムの振る舞いを自動的にテストし、コードの信頼性を高めることができます。
fdiv-inf-nan.zig:
const std = @import("std");
const expect = std.testing.expect;
fn fdiv(n: f64, d: f64) f64 {
    return n / d;
}
const inf = std.math.inf(f64);
const nan = std.math.nan(f64);
test "fdiv 1" {
    try expect(fdiv(123.0, 111.1) == 123.0 / 111.1);
}
test "fdiv 2" {
    try expect(fdiv(123.0, 0.0) == inf);
}
test "fdiv 3" {
    try expect(fdiv(0.0, 0.0) == nan);
}
コマンドライン:
% zig version
0.11.0
% zig test fdiv-inf-nan.zig 
Test [3/3] test.fdiv 3... FAIL (TestUnexpectedResult)
/usr/local/lib/zig/std/testing.zig:515:14: 0x2248df in expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/usr/home/user1/tut/zig/fdiv-inf-nan.zig:20:5: 0x224b0b in test.fdiv 3 (test)
    try expect(fdiv(0.0, 0.0) == nan);
    ^
2 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
/home/user1/.cache/zig/o/00aa6779b54996b883ad0fc42233ea3d/test
この例では、Zig言語で fdiv-inf-nan.zig というファイルに記述されたテストコードが示されています。このコードは、fdiv 関数に対する3つのテストケースを含んでいます。
"fdiv 1": 正常な割り算が行われることを確認します。fdiv(123.0, 111.1) が 123.0 / 111.1 と等しいかどうかを期待します。
"fdiv 2": 0で割った場合、無限大を返すことを確認します。fdiv(123.0, 0.0) が inf と等しいかどうかを期待します。
"fdiv 3": 0を0で割った場合、非数を返すことを確認します。fdiv(0.0, 0.0) が nan と等しいかどうかを期待します。
テストを実行すると、2つのテストがパスし、1つのテストが失敗します。失敗したテストケースは "fdiv 3" です。このテストは、 fdiv(0.0, 0.0) が nan と等しいかどうかを検証しますが、実際にはそうではありませんでした。
この失敗は、期待される結果と実際の結果が一致しなかったことを示しています。この情報を元に、fdiv 関数の実装やテストコードを再検討することで、この問題を修正することができます。
fdiv-inf-nan-FIX.zig:
const std = @import("std");
const expect = std.testing.expect;
fn fdiv(n: f64, d: f64) f64 {
    return n / d;
}
const inf = std.math.inf(f64);
const nan = std.math.nan(f64);
const isNan = std.math.isNan;
test "fdiv 1" {
    try expect(fdiv(123.0, 111.1) == 123.0 / 111.1);
}
test "fdiv 2" {
    try expect(fdiv(123.0, 0.0) == inf);
}
test "fdiv 3" {
    try expect(isNan(fdiv(0.0, 0.0)));
}
コマンドライン:
% zig test fdiv-inf-nan-FIX.zig 
All 3 tests passed.
修正された fdiv-inf-nan-FIX.zig ファイルでは、3番目のテストケースが修正されています。その変更箇所と結果について解説します。
 
この修正では、fdiv(0.0, 0.0) の結果が nan であるかどうかを確認するために、 std.math.isNan 関数を使用しています。この関数は、与えられた浮動小数点数が非数 (nan) であるかどうかを判定します。
修正後のコードを実行すると、全てのテストケースが成功し、期待通りの結果が得られました。
 
この修正により、fdiv 関数が非数 (nan) を返す場合に正常にテストが通るようになりました。
Zigでは、変数は名前を持った連続したメモリー領域で、型を持ちます。
変数は、宣言が必要です。
var キーワードまたは const キーワードを使用して変数を宣言します。const を使用することが一般的であり、これによりコードの読みやすさが向上し、最適化の機会が増えます。
extern キーワードや @extern 組み込み関数を使用して、他のオブジェクトからエクスポートされた変数とリンクすることができます。
コンテナレベルの変数は、静的なライフタイムを持ち、コンテナ内で宣言されるため、コンテナが評価されると初期化されます。これらは、構造体、共用体、列挙型、または不透明な型の内部で宣言することができます。
関数内でコンテナを使用することで、静的なローカル変数を作成することも可能です。
threadlocal キーワードを使用して、スレッドごとに異なる変数インスタンスを作成することができます。
関数内や comptime ブロック内で使用される変数は、ローカル変数と呼ばれます。これらは、関数やブロックのスコープ内でのみ有効です。
comptime キーワードを使用することで、コンパイル時に値がわかる変数を定義することができます。
Zig言語では、これらの変数のタイプやスコープを利用して、効率的で安全なプログラミングが可能です。
Zigでは型に名前を付けるためにも変数が使われます。
型を保持するconst変数の例:
const Complex = struct {
    real: f64,
    imag: f64,
};
const Colour = enum {
    red,
    green,
    blue,
};
const Number = union {
    int: i64,
    float: f64,
};
構文(EBNF):
var-decl = ( "const" | "var" ) IDENTIFIER [ ":" type-expr ] [ byte-align ] [ link-section ] [ "=" expr ] ";"
var-decl = const-var-decl | var-var-decl
const-var-decl = "const" IDENTIFIER [ ":" type-expr ] [ byte-align ] [ link-section ] "=" expr ";"
var-var-decl = "var" IDENTIFIER ":" type-expr [ byte-align ] [ link-section ] "=" expr ";"
と意味論も加味するとなります。
var 変数の場合は、不定値としてプリミティブ値の undefined で初期化することができます[https://ziglang.org/documentation/master/#undefined undefined]。
undefinedは、任意の型に強制( ''be coerced'' )することができます。一旦これが起こると、値がundefinedであることを検出することができなくなります。 undefinedは、値が何にでもなり得ることを意味し、型によれば無意味なものでさえもあり得ることを意味します。英語で undefinedは "Not a meaningful value. Using this value would be a bug. The value will be unused, or overwritten before being used."（意味のない値。この値を使うとバグになる。この値は使われないか、使われる前に上書きされるでしょう）という意味です（ただしローカル変数が使われないと、error: unused local variable となります）。
キーワード const で宣言された変数は、必ず初期化が必要で、宣言以降は値を変更することはできません（イミュータブル）。
const 変数の宣言のとき型が省略されると、初期値から型をコンパイラーが決めてくれます（型推論）。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const i = 0;
    try stdout.print("i = {}\n", .{i}); 
}
実行結果:
i = 0
このコードは、const キーワードを使って定数を宣言し、その値を出力しています。
以下に、このコードの各部分の解説を示します。
const std = @import("std");：std という名前で標準ライブラリをインポートします。これにより、標準ライブラリの機能を使うことができます。
const stdout = std.io.getStdOut().writer();：標準出力を表す stdout という名前のライターを取得します。これにより、プログラムは標準出力にテキストを書き込むことができます。
pub fn main() !void {：プログラムのエントリーポイントである main 関数を宣言します。この関数は、戻り値としてエラーを返す可能性があるため、!void 型を返します。pub キーワードは、この関数が外部からアクセス可能であることを示します。
const i = 0;：i という名前の定数を宣言し、初期値として整数 0 を与えます。この定数は後で使用されます。
try stdout.print("i = {}\n", .{i});：標準出力に文字列を書き込むために print メソッドを使用します。"i = {}\n" の部分はフォーマット文字列であり、{} の位置に i の値が挿入されます。.{i} は、変数 i の値を挿入するための特別な構文です。
キーワード var で変数を宣言するときも初期化は必須です。またいつでも値を変更することはできます。
var 変数の宣言では、型を省略することはできません。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var i : isize = 0;
    try stdout.print("i = {}\n", .{i});
    i = 12;
    try stdout.print("i = {}\n", .{i});
    i *= i;
    try stdout.print("i = {}\n", .{i}); 
}
実行結果:
i = 0
i = 12
i = 144
このコードは、var キーワードを使用して可変の変数を宣言し、その値を変更しています。
以下に、このコードの各部分の解説を示します。
const std = @import("std");：標準ライブラリを std としてインポートします。
const stdout = std.io.getStdOut().writer();：標準出力を表す stdout という名前のライターを取得します。
pub fn main() !void {：プログラムのエントリーポイントである main 関数を宣言します。この関数は、エラーを返す可能性があるため、!void 型を返します。
var i : isize = 0;：i という名前の変数を宣言し、型として isize を指定して初期値として整数 0 を与えます。
try stdout.print("i = {}\n", .{i});：標準出力に文字列を書き込むために print メソッドを使用します。"i = {}\n" の部分はフォーマット文字列であり、{} の位置に i の値が挿入されます。
i = 12;：変数 i の値を 12 に変更します。
try stdout.print("i = {}\n", .{i});：print メソッドを使用して、変更後の i の値を標準出力に出力します。
i *= i;：変数 i の値を自乗して再代入します。
try stdout.print("i = {}\n", .{i});：print メソッドを使用して、再代入後の i の値を標準出力に出力します。
変数は、外部スコープの変数をシャドーイングすることは許されません[https://ziglang.org/documentation/master/#Identifiers Identifiers]。
[https://wandbox.org/permlink/yRqEgKgW7ltWYjOc 外部スコープの識別子をシャドーイングすることは許されません]:
const x = 0;
pub fn main() !void {
    var x : isize = 1;
}
コンパイル結果:
main.zig:3:9: error: local variable shadows declaration of 'x' 
main.zig:1:1: note: declared here
 (exit status 1)
エラーメッセージによれば、外部スコープで宣言された x がローカルスコープで再度宣言されたため、エラーが発生したことがわかります。Zigでは、外部スコープの変数をシャドーイングすることは許されていないため、このコードはコンパイルエラーとなります。
識別子は英数字かアンダースコアで始まり、英数字かアンダースコアがいくつでも続くことができます。また、キーワードと重なってはいけません。
外部ライブラリーとのリンクなど、これらの要件に適合しない名前が必要な場合は、@""構文を使用することができます。
const @"identifier with spaces in it" = 0xff;
const @"1SmallStep4Man" = 112358;
const c = @import("std").c;
pub extern "c" fn @"error"() void;
pub extern "c" fn @"fstat$INODE64"(fd: c.fd_t, buf: *c.Stat) c_int;
const Color = enum {
    red,
    @"really red",
};
const color: Color = .@"really red";
このコードは、Zigで識別子にスペースが含まれる場合や、数字で始まる識別子を使用する方法、および外部C関数に識別子を付ける方法を示しています。
以下に、各部分の解説を示します。
const @"identifier with spaces in it" = 0xff;：スペースが含まれる識別子を使用して、定数を宣言しています。Zigでは、ダブルクォートで囲まれた文字列を識別子として使用することができます。
const @"1SmallStep4Man" = 112358;：数字で始まる識別子を使用して、定数を宣言しています。同様に、ダブルクォートで囲まれた文字列を識別子として使用しています。
const c = @import("std").c;：外部ライブラリをインポートして、その一部を c という名前で定数として使用します。これにより、外部のCライブラリで定義された型や関数にアクセスすることができます。
pub extern "c" fn @"error"() void;：外部C関数に識別子を付けて宣言しています。extern "c" はC言語の呼び出し規約を指定し、void は戻り値の型を示しています。
pub extern "c" fn @"fstat$INODE64"(fd: c.fd_t, buf: *c.Stat) c_int;：同様に、外部C関数に識別子を付けて宣言しています。この関数は引数を取り、c_int 型を戻り値として返します。
const Color = enum { red, @"really red", };：列挙型 Color を定義しています。@"really red" のように、列挙子にも識別子を付けることができます。
const color: Color = .@"really red";：Color 列挙型の変数 color を宣言し、@"really red" の列挙子を初期値として指定しています。
このように、Zigでは識別子にスペースや数字を含めることができ、また識別子を外部C関数に付けることもできます。これにより、異なる名前付け規則を持つライブラリや関数をZigのコードから使用することができます。
Zigでは、さまざまな整数リテラルの表現がサポートされています。
構文(EBNF):
INTEGER = "0b" bin_int skip
        | "0o" oct_int skip
        | "0x" hex_int skip
        |      dec_int skip
skip = ([ \n] | line_comment)*
bin = "0" | "1"
bin_ = [ '_' ] bin
oct = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
oct_ = [ '_' ] oct
hex = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
    | "a" | "b" | "c" | "d" | "e" | "f"
    | "A" | "B" | "C" | "D" | "E" | "F"
hex_ = [ '_' ] hex
dec =  "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
dec_ = [ '_' ] dec
bin_int = bin bin_*
oct_int = oct oct_*
dec_int = dec dec_*
hex_int = hex hex_*
。
2進数・8進数・16進数はそれぞれ 0b・0o・0x を前置します。
他の多くの言語と異なり 0 の次の文字は小文字が必須で、大文字は受け付けません。
整数リテラルにはサイズ制限がなく、不明な動作が発生する場合にはコンパイラがそれをキャッチします。
ただし、整数値がコンパイル時には不明である場合、サイズが判明している必要があり、未定義の動作の影響を受けます。
fn divide(a: i32, b: i32) i32 {
    return a / b;
}
 
このような関数では、値 a と b は実行時のみに判明するため、この除算操作は整数オーバーフローやゼロ除算の影響を受ける可能性があります。
整数演算では、+ や - などの演算子は整数オーバーフローに対して未定義の動作を引き起こします。代わりに、すべてのターゲットに対してラッピングおよびサチュレーティング演算を行うための代替演算子が提供されています。
Zigでは任意のビット幅の整数もサポートされており、i または u の後に数字が続く識別子を使用して参照されます。たとえば、i7 は7ビットの符号付き整数を示します。符号付き整数型の場合、Zigでは2の補数表現が使用されます。
[https://codapi.org/embed/?sandbox=zig&code=const+std+%3D+%40import%28%22std%22%29%3B%0Aconst+stdout+%3D+std.io.getStdOut%28%29.writer%28%29%3B%0A%0Apub+fn+main%28%29+%21void+%7B%0A++++var+i%3A+i5+%3D+0%3B%0A++++var+j%3A+i4+%3D+0%3B%0A++++var+k%3A+i4+%3D+0%3B%0A%0A++++while+%28i+%3C+20%29+%7B%0A++++++++try+stdout.print%28%22i%3A%7B%7D%2C+j%3A%7B%7D%2C+k%3A%7B%7D%5Cn%22%2C+.%7B+i%2C+j%2C+k+%7D%29%3B%0A++++++++i+%3D+i+%2B+1%3B%0A++++++++j+%3D+j+%2B%25+1%3B%0A++++++++k+%3D+k+%2B%7C+1%3B%0A++++%7D%0A%7D ラッピング演算およびサチュレーティング演算とオーバーフローの例]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var i: i5 = 0;
    var j: i4 = 0;
    var k: i4 = 0;
    while (i < 20) {
        try stdout.print("i:{}, j:{}, k:{}\n", .{ i, j, k });
        i = i + 1;
        j = j +% 1;
        k = k +| 1;
    }
}
実行結果:
i:0, j:0, k:0
i:1, j:1, k:1
i:2, j:2, k:2
i:3, j:3, k:3
i:4, j:4, k:4
i:5, j:5, k:5
i:6, j:6, k:6
i:7, j:7, k:7
i:8, j:-8, k:7
i:9, j:-7, k:7
i:10, j:-6, k:7
i:11, j:-5, k:7
i:12, j:-4, k:7
i:13, j:-3, k:7
i:14, j:-2, k:7
i:15, j:-1, k:7
thread 1 panic: integer overflow
/sandbox/src/main.zig:11:15: 0x21e650 in main (main)
        i = i + 1;
              ^
/usr/lib/zig/std/start.zig:574:37: 0x21e4fe in posixCallMainAndExit (main)
            const result = root.main() catch |err| {
                                    ^
/usr/lib/zig/std/start.zig:243:5: 0x21dfe1 in _start (main)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
 (exit status 139)
このZigのプログラムは、異なるビット幅の整数型を使用し、それらの振る舞いを示しています。以下にコードの解説を示します。
var i: i5 = 0;：i という名前の変数を宣言し、初期値として 0 を与えます。この変数は i5 型であり、5ビットの符号付き整数を表します。
var j: i4 = 0;：j という名前の変数を宣言し、初期値として 0 を与えます。この変数は i4 型であり、4ビットの符号付き整数を表します。
var k: i4 = 0;：k という名前の変数を宣言し、初期値として 0 を与えます。この変数も i4 型であり、4ビットの符号付き整数を表します。
while (i < 20) { ... }：i の値が20未満の間、ループが続きます。
i = i + 1;：変数 i の値を1増加させます。この演算は、i5 型の整数の範囲を超える可能性があり、後に整数オーバーフローが発生します。
j = j +% 1;：変数 j の値を1増加させます。ここではラップアラウンド演算子 +% を使用しています。これにより、j の値が範囲外になる場合は0に戻ります。
k = k +| 1;：変数 k の値を1増加させます。ここでは飽和演算子 +| を使用しています。これにより、k の値が範囲外になる場合は最大値に飽和します。
プログラムの実行結果から、i の値がオーバーフローし、未定義の振る舞いが発生しました。これは i5 型の整数が範囲を超えたためです。このような場合、Zigは整数オーバーフローを検出してプログラムをパニックさせます。一方で、j の値はラッピング演算子により範囲外になることなく、k の値は飽和演算子により最大値で飽和しています。
Zigでは、以下の浮動小数点数型がサポートされています：
浮動小数点数リテラルは、comptime_float型を持ち、最大の他の浮動小数点数型（f128）と同じ精度と操作を保証します。
浮動小数点数リテラルは、任意の浮動小数点数型および整数型（小数点以下がない場合）に変換されます。
const floating_point = 123.0E+77;
const another_float = 123.0;
const yet_another = 123.0e+77;
const hex_floating_point = 0x103.70p-5;
const another_hex_float = 0x103.70;
const yet_another_hex_float = 0x103.70P-5;
構文(EBNF):
FLOAT = "0x" hex_int "." hex_int [ ( "p" | "P" ) [ "-" | "+" ] dec_int ] skip
      |      dec_int "." dec_int [ ( "e" | "E" ) [ "-" | "+" ] dec_int ] skip
      | "0x" hex_int ( "p" | "P" ) [ "-" | "+" ] dec_int skip
      |      dec_int ( "e" | "E" ) [ "-" | "+" ] dec_int skip
。
10進数のほか、16進数の浮動小数点数リテラルに対応していますが、2進数・8進数の浮動小数点数リテラルには対応していません。
16進数の浮動小数点数リテラルも、指数部は10進数です。
NaN、無限大、負の無限大のための構文はありません。これらの特殊な値については、標準ライブラリを使用する必要があります。
const std = @import("std");
const inf = std.math.inf(f32);
const negative_inf = -std.math.inf(f64);
const nan = std.math.nan(f128);
デフォルトでは、浮動小数点数の演算はStrictモードを使用しますが、ブロックごとにOptimizedモードに切り替えることができます。
export fn foo_strict(x: f64) f64 {
    return x + big - big;
}
export fn foo_optimized(x: f64) f64 {
    @setFloatMode(.Optimized);
    return x + big - big;
}
上記の例では、演算をStrictモードとOptimizedモードで比較しています。
Zigの演算子の概要は以下の通りです：
演算子の詳細は、Zigのドキュメントやリファレンスを参照してください。
Zigは、やや関数型プログラミング言語の影響を受けており、多くの構文が値を持ちます。
Zigの制御構造の多くは式構文と文構文を持ちます（例外は #switch で式構文しかありません）。
以下、Kotlin#分岐から、一部の例を移植しました。
Zigには、#if と #switch の２つの分岐構文があります。
Zigでは、if は値を分岐する if式 とブロックを分岐する if文 があります。
構文(EBNF)[https://ziglang.org/documentation/master/#Grammar Grammar]から抜粋。原文はPEG(Parsing Expression Grammar)なので終端子の記法などはアレンジしています。:
if-expr = if-prefix expr [ "else" [ payload ] expr ]
if-prefix = "if" "(" expr ")" [ ptr-payload ]
payload = "|" IDENTIFIER "|"
ptr-payload = "|" [ "*" ] IDENTIFIER "|"
if-statement = if-prefix block-expr [ "else" [ payload ] statement ]
             | if-prefix assign-expr ( ”;” | "else" [ payload ] statement )
block-expr = [ block-label ] block
block-label = IDENTIFIER ":"
block = "{" statement* "}"
assign-expr = expr [ assign-op expr ]
assign-op = "+=" | "-=" | "*=" | "/=" ...（略）
[https://wandbox.org/permlink/QcMQ37j87DHcVBBA if式の例]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const i = 0;
    if (i == 0)
        try stdout.writeAll("zero\n") // ここに ; があるとエラーになります。
    else
        try stdout.writeAll("non zero\n");
    try stdout.print(if (i == 0)
        "ゼロ\n"
    else
        "非ゼロ\n", .{});
}
実行結果:
zero 
ゼロ
if文に変更:
    if (i == 0) {
        try stdout.writeAll("zero\n"); // ブロック中ならば ; があってもエラーになりません。
    } else {
        try stdout.writeAll("non zero\n");
    }
と ; を使うことができますこの場合、文法的には if-expr（if式）ではなく if-statment（if文）になります。。
if式で、条件が成立せずelseを省略されたとき、式の値は void となります。
[https://wandbox.org/permlink/GAyh2Mk0KK0AFLwT 条件不成立でelseを省略すると]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    try stdout.print("if (false) 1 => {}\n", .{ if (false) 1 });
}
実行結果:
if (false) 1 => void
[https://wandbox.org/permlink/OCdAr0VGlXu42dOK 条件式に整数を使うと]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const i = 0;
    if (i) {
        try stdout.writeAll("not zero");
    }
}
./main.zig:7:9: error: expected type 'bool', found 'comptime_int'
    if (i) { 
        ^
Zigでは、if に限らず、条件式は、bool、オプショナル型あるいはエラーユニオン型でなければいけません。
    if (i != 0) {
とします。
ifの条件式にはオプショナル型( ?T )を使うことが出来ます。この場合は、通常の値のほか null を想定でき、null に出会った場合は else 節が実行されます。
[https://codapi.org/embed/?sandbox=zig&code=const+std+%3D+%40import%28%22std%22%29%3B%0Aconst+stdout+%3D+std.io.getStdOut%28%29.writer%28%29%3B%0A%0Apub+fn+main%28%29+%21void+%7B%0A++++var+a%3A+%3Fu16+%3D+42%3B%0A++++if+%28a%29+%7C*value%7C+%7B%0A++++++++try+stdout.print%28%22value.*+%3D+%7B%7D%28%7B%7D%29%5Cn%22%2C+.%7Bvalue.*%2C+%40TypeOf%28value.*%29%7D%29%3B%0A++++++++value.*+%3D+123%3B%0A++++%7D+else+%7B%0A++++++++unreachable%3B%0A++++%7D%0A++++if+%28a%29+%7Cn%7C+%7B%0A++++++++try+stdout.print%28%22n+%3D+%7B%7D%28%7B%7D%29%5Cn%22%2C+.%7Bn%2C+%40TypeOf%28n%29%7D%29%3B%0A++++%7D+else+%7B%0A++++++++unreachable%3B%0A++++%7D%0A++++a+%3D+null%3B%0A++++if+%28a%29+%7C_%7C+%7B%0A++++++++unreachable%3B%0A++++%7D+else+%7B%0A++++++++try+stdout.print%28%22a+%3D+%7B%3F%7D%28%7B%7D%29%5Cn%22%2C+.%7Ba%2C+%40TypeOf%28a%29%7D%29%3B%0A++++%7D%0A%7D nullable-if.zig]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var a: ?u16 = 42;
    if (a) |*value| {
        try stdout.print("value.* = {}({})\n", .{value.*, @TypeOf(value.*)});
        value.* = 123;
    } else {
        unreachable;
    }
    if (a) |n| {
        try stdout.print("n = {}({})\n", .{n, @TypeOf(n)});
    } else {
        unreachable;
    }
    a = null;
    if (a) |_| {
        unreachable;
    } else {
        try stdout.print("a = {?}({})\n", .{a, @TypeOf(a)});
    }
}
value.* = 42(u16)
n = 123(u16)
a = null(?u16)
 
このコードでは、aという名前のオプショナル型の変数を定義し、最初に値 42 で初期化しています。
次に、最初のif文では、aがnullでない場合にブロック内の処理が実行されます。この場合、|*value|構文を使ってオプショナル型の値を取り出し、そのポインターをvalueに割り当てています。その後、取り出した値を出力してから、その値を123に書き換えています。
2番目のif文も同様に、aがnullでない場合にブロック内の処理が実行されます。ただし、こちらでは|n|の構文を使ってオプショナル型の値そのものを取り出し、その値を出力しています。
最後のif文では、aにnullを割り当てています。その後、aがnullであるかどうかをチェックし、nullの場合にはelseブロック内の処理が実行されます。ここでは、|_|の構文を使って、取り出す値がないことを示しています。その後、aがnullであることを出力しています。
実行結果を見ると、それぞれのif文が条件に応じて正しく動作していることがわかります。
ifの条件式にはエラーユニオン型( !T )を使うことが出来ます。この場合は、通常の値のほかエラーコードを想定でき、エラーコードに出会った場合は else |err| 節が実行され、err がエラーコードです。
[https://codapi.org/embed/?sandbox=zig&code=const+std+%3D+%40import%28%22std%22%29%3B%0Aconst+stdout+%3D+std.io.getStdOut%28%29.writer%28%29%3B%0A%0Apub+fn+main%28%29+%21void+%7B++++%0A++++var+b%3A+anyerror%21u32+%3D+error.BadValue%3B%0A++++try+stdout.print%28%22b+%3D+%7Bany%7D%28%7B%7D%29%5Cn%22%2C+.%7Bb%2C+%40TypeOf%28b%29%7D%29%3B%0A++++if+%28b%29+%7C_%7C+%7B%0A++++++++unreachable%3B%0A++++%7D+else+%7Cerr%7C+%7B%0A++++++++try+stdout.print%28%22err+%3D+%7B%3F%7D%28%7B%7D%29%5Cn%22%2C+.%7Berr%2C+%40TypeOf%28err%29%7D%29%3B%0A++++%7D%0A++++b+%3D+4423%3B%0A++++try+stdout.print%28%22b+%3D+%7Bany%7D%28%7B%7D%29%5Cn%22%2C+.%7Bb%2C+%40TypeOf%28b%29%7D%29%3B%0A++++if+%28b%29+%7Cn%7C+%7B%0A++++++++try+stdout.print%28%22n+%3D+%7B%7D%28%7B%7D%29%5Cn%22%2C+.%7Bn%2C+%40TypeOf%28n%29%7D%29%3B%0A++++%7D+else+%7C_%7C+%7B%0A++++++++unreachable%3B%0A++++%7D%0A%7D errorunion-if.zig]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {    
    var b: anyerror!u32 = error.BadValue;
    try stdout.print("b = {any}({})\n", .{b, @TypeOf(b)});
    if (b) |_| {
        unreachable;
    } else |err| {
        try stdout.print("err = {?}({})\n", .{err, @TypeOf(err)});
    }
    b = 4423;
    try stdout.print("b = {any}({})\n", .{b, @TypeOf(b)});
    if (b) |n| {
        try stdout.print("n = {}({})\n", .{n, @TypeOf(n)});
    } else |_| {
        unreachable;
    }
}
b = error.BadValue(anyerror!u32)
err = error.BadValue(anyerror)
b = 4423(anyerror!u32)
n = 4423(u32)
 
このコードでは、bという名前のエラーユニオン型の変数を定義し、最初にerror.BadValueというエラーコードを割り当てます。
次に、最初のif文では、bがエラーコードを持っている場合にはelse |err|ブロック内の処理が実行されます。この場合、|err|の構文を使ってエラーコードを取り出し、そのエラーコードをデバッグ出力しています。
2番目のif文も同様に、bがエラーコードを持っている場合にはelse |_|ブロック内の処理が実行されます。ただし、こちらでは|n|の構文を使ってエラーコードの値そのものを取り出し、その値をデバッグ出力しています。
実行結果を見ると、それぞれのif文が条件に応じて正しく動作していることがわかります。最初の場合ではエラーコードが出力され、2番目の場合では正常な値が出力されています。
Zigでは、switch は式で値を返します。switch文はありません。switch-prong（分岐先）の値の型は一致している必要があります。
構文(EBNF):
switch-expr = "switch" "(" expr ")" "{" switch-prong-list "}"
switch-prong-list = (switch-prong "," )* [ switch-prong ]
switch-prong = switch-case "=>" [ ptr-payload ] assign-expr
switch-case = switch-item ( "," switch-item )* [ "," ] | "else"
switch-item = expr [ "..." expr ]
[https://wandbox.org/permlink/xhVUSAwvUzfCqnTi switch.zig]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const ary = .{1, 'Z', 3.14, .{1,2,3}, true, void, null, i64};
    inline for (ary) |obj| {
        switch (@typeInfo(@TypeOf(obj))) {
            .ComptimeInt => try stdout.print("ComptimeInt: {}\n", .{obj}),
            .ComptimeFloat => try stdout.print("ComptimeFloat: {}\n", .{obj}),
            .Struct => try stdout.print("Struct: {}\n", .{obj}),
            .Bool => try stdout.print("Bool: {}\n", .{obj}),
            .Type => try stdout.print("Type: {}\n", .{obj}),
            .Null => try stdout.print("Null: {}\n", .{obj}),
            else => try stdout.print("{}\n", .{@typeInfo(@TypeOf(obj))})
        }
    } 
}
実行結果:
ComptimeInt: 1
ComptimeInt: 90
ComptimeFloat: 3.14e+00
Struct: { 1, 2, 3 }
Bool: true
Type: void
Null: null 
Type: i64
Zigには、#while と #for の２つの反復構文があります。
whileは条件が成立している間、繰り返しを行います。条件がブロック内で評価され、条件が偽になるまで続きます。whileにはelse節もあり、ループ完了後に実行されます。また、ラベルを付けてネストしたループからのbreakやcontinueも可能です。オプショナル型やエラーユニオン型の条件も受け付け、それらの値をキャプチャできます。インライン化も可能で、コンパイル時の最適化や型の利用が可能です。
構文(EBNF):
loop-expr = [ "inline" ] ( for-expr | while-expr )
while-expr = while-prefix expr [ "else" [ payload ] expr ]
while-prefix = "while" "(" expr ")" [ ptr-payload ] [ while-continue-expr ]
while-continue-expr = ":" "(" assign-expr ")"
while-statement = while-prefix block-expr [ "else" [ pay-load ] statement ]
                | while-prefix assign-expr ( ”;” | "else" [ payload ] statement )
多くの構文要素は if と共通しているので #if の構文も参照してください。
Zigも、else を伴うことのできる while です。
while.zig:
pub fn main() !void {
    var i: usize = 1;
    while (i < 50) : (i += 1) {
        try stdout.print("{}! == {}\n", .{ i, fib(i) });
    }
}
fn fib(n: usize) usize {
    return if (n < 2) n else fib(n - 1) + fib(n - 2);
}
const stdout = std.io.getStdOut().writer();
const std = @import("std");
実行結果:
An error occurred:
1! == 1
2! == 1
3! == 2
4! == 3
5! == 5
6! == 8
7! == 13
8! == 21
9! == 34
10! == 55
11! == 89
12! == 144
13! == 233
14! == 377
15! == 610
16! == 987
17! == 1597
18! == 2584
19! == 4181
20! == 6765
21! == 10946
22! == 17711
23! == 28657
24! == 46368
25! == 75025
26! == 121393
27! == 196418
28! == 317811
29! == 514229
30! == 832040
31! == 1346269
32! == 2178309
33! == 3524578
34! == 5702887
35! == 9227465
36! == 14930352
37! == 24157817
38! == 39088169
39! == 63245986
    while (i < 50) : (i += 1) {
        try stdout.print("{}! == {}\n", .{ i, fib(i) });
    }
    while (i < 50) {
        try stdout.print("{}! == {}\n", .{ i, fib(i) });
        i += 1;
    }
と等価で、追加の式はC言語の for (;;) の三項目にあたります。
while-with-else.zig:
const stdout = std.io.getStdOut().writer();
const std = @import("std");
pub fn main() !void {
    var i: usize = 0;
    while (i < 5) : (i += 1) {
        try stdout.print("{} ", .{i});
    } else {
        try stdout.writeAll("done!\n");
    }
    i = 0;
    while (i < 50) : (i += 1) {
        try stdout.print("{} ", .{i});
        if (i == 10) {
            try stdout.writeAll("break!\n");
            break;
        }
    } else {
        try stdout.writeAll("done!\n");
    }
}
実行結果:
0 1 2 3 4 done!
0 1 2 3 4 5 6 7 8 9 10 break!
while のループを「完走」すると、else 以降が実行されます。
もし break などで中断されると、else 以降は実行されません。
{{コラム|elseを伴ったwhileの使いどころ|2=
Zigのwhileループは完走すると実行するelse節を置くことができます。
では、このelse節は何に使うのでしょう？
1000以下の素数を求める（フラッグ版）:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var i: usize = 2;
    while (i <= 1000) : (i += 1) {
        var j: usize = 2;
        var is_prime = true;
        while (j * j <= i) : (j += 1) {
            if (i % j == 0) {
                is_prime = false;
                break;
            }
        }
        if (is_prime)
            try stdout.print("{} ", .{i});
    }
}
1000以下の素数を求める（else版）:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var i: usize = 2;
    while (i <= 1000) : (i += 1) {
        var j: usize = 2;
        while (j * j <= i) : (j += 1) {
            if (i % j == 0) {
                break;
            }
        } else try stdout.print("{} ", .{i});
    }
}
フラッグがなくなり簡素になりました。
Zig以外の言語では、Pythonでも else を伴ったループ構文があります。
whileループにラベルを付けると、ネストしたループ内からのbreakやcontinueから参照できます[https://ziglang.org/documentation/master/#Labeled-while Labeled while]。
ラベル付きwhile:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var i: u16 = 0;
    var j: u16 = 0;
    outer: while (i < 15) : (i += 1) {
       j = 0;
        while (j < 15) : (j += 1) {
            if (i == 7 and j == 11) {
                break :outer;
            }
        }
    }
    try stdout.print("i = {}, j = {}\n", .{ i, j });
}
実行結果:
i = 7, j = 11
ifと同じように、whileループは条件としてオプショナル型の値を受け取り、ペイロードをキャプチャすることができます。null に遭遇した場合、ループは終了します[https://ziglang.org/documentation/master/#while-with-Optionals while with Optionals]。
while 式に |x| 構文がある場合、while 条件はオプショナル型（あるいは次で述べるエラーユニオン型）でなければなりません（この x かキャプチャされたペイロードです）。
[https://wandbox.org/permlink/fFmMl6h4567ILpTt オプショナル型を条件としたwhile]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var sum: u32 = 0;
    while (sequence()) |n| {
        sum += n;
    }
    try stdout.print("sum = {}\n", .{sum});
}
fn sequence() ?u32 {
    const S = struct {
        var x: u32 = 5;
    };
    return if (S.x == 0) null else blk: {
        S.x -= 1;
        break :blk S.x;
    };
}
実行結果:
sum = 10
ifと同じように、whileループは条件としてエラーユニオン型の値を受け取り、ペイロードをキャプチャすることができます。エラーコードに遭遇した場合、ループは終了します[https://ziglang.org/documentation/master/#while-with-Error-Unions while with Error Unions]。
while 式に |x| 構文がある場合、while 条件はエラーユニオン型（あるいは前で述べたオプショナル型）でなければなりません（この x かキャプチャされたペイロードです）。
while 式に else |x| 構文がある場合、while 条件にエラーユニオン型が必要です。
[https://wandbox.org/permlink/r8xbAanhVSDHyIws エラーユニオン型を条件としたwhile]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var sum: u32 = 0;
    while (sequence()) |n| {
        sum += n;
    } else |err| {
        try stdout.print("err = {}\n", .{err});
    }
    try stdout.print("sum = {}\n", .{sum});
}
fn sequence() !u32 {
    const S = struct {
        var x: u32 = 5;
    };
    return if (S.x == 0) error.ReachedZero else blk: {
        S.x -= 1;
        break :blk S.x;
    };
}
実行結果:
err = error.ReachedZero 
sum = 10
whileループはインライン化することができる。これにより、ループが展開され、コンパイル時にしかできないこと、例えば、型をファーストクラスの値として使用することなどができるようになります[https://ziglang.org/documentation/master/#inline-while inline while]。
[TODO:コード例]
Zigのforループは、スライスや配列をイテレートする際に柔軟性を提供します。要素ごとに処理を行い、continueやbreakを使用して制御フローを操作できます。インデックスや複数のオブジェクトの同時イテレーション、参照によるイテレーションなど、多彩な機能を持ちます。また、forループを式として使用することも可能で、else節を使ってループが完了した際に特定の処理を行うことができます。ラベル付きループやインライン化もサポートされ、効率的なコーディングを実現します。
構文(EBNF):
for-statement = "for" for-prefix block-expr [ "else" statement ]
for-expr = "for" for-prefix expr [ "else" expr ]
for-prefix = "(" for-arguments-list ")" ptr-list-payload
for-arguments-list = for-item { "," for-item } [ "," ]
for-item = expr [ ".." expr]
ptr-list-payload = "|" [ "*" ] IDENTIFIER { "," [ "*" ] IDENTIFIER } [ "," ] "|"
。
for-ary.zig:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const int = i16;
    const ary = [_]int{ 1, 5, 4, 6, 4, 9 };
    var sum: int = 0;
    for (ary, 0..) |n, i| {
        try stdout.print("n = {}, i = {}\n", .{ n, i });
        sum += n;
    }
    try stdout.print("sum = {}\n", .{sum});
    var ary2 = ary;
    for (&ary2) |*r| {
        r.* += 10;
    }
    for (ary2) |n| {
        try stdout.print("{}, ", .{n});
    }
}
実行結果:
n = 1, i = 0
n = 5, i = 1
n = 4, i = 2
n = 6, i = 3
n = 4, i = 4
n = 9, i = 5
sum = 29
11, 15, 14, 16, 14, 19, 
[https://wandbox.org/permlink/BOuIKx4BYffBh5Bk for-str.zig]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    const str = "simple string";
    for (str) |c| {
        try stdout.print("{c} ", .{c});
    }
}
実行結果:
s i m p l e   s t r i n g 
for-range.zig:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    for (23..43) |i| {
        try stdout.print("{} ", .{i});
    }
}
実行結果:
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 
ラベル付きfor( ''Labeled for'' )とは、ラベルを伴った for ループでラベルも for 構文の一部です。for ループにラベルを付けると、ネストしたループ内からのbreakやcontinueから参照できます[https://ziglang.org/documentation/master/#Labeled-for Labeled for]。
ラベル付きfor:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var x: usize = undefined;
    var y: usize = undefined;
    loop_top: for ("Hello", 0..) |co, i| {
        for ("World", 0..) |ci, j| {
            if (co == ci) {
                try stdout.print("c = '{c}'({}, {})\n", .{ ci, i, j });
                x = i;
                y = j;
                break :loop_top;
            }
        }
    }
    try stdout.print("x = {}, y = {}\n", .{ x, y });
}
実行結果:
c = 'l'(2, 3) 
x = 2, y = 3
Forループはインライン化することができます。これにより、ループが展開され、コンパイル時にしかできないこと、例えば、型をファーストクラスの値として使用することなどができるようになります。インライン化されたforループのキャプチャ値とイテレータ値は、コンパイル時既知です[https://ziglang.org/documentation/master/#inline-for inline for]。
inline for:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var x: usize = undefined;
    var y: usize = undefined;
    loop_top: inline for ("Hello", 0..) |co, i| {
        inline for ("World", 0..) |ci, j| {
            if (co == ci) {
                try stdout.print("c = '{c}'({}, {})\n", .{ ci, i, j });
                x = i;
                y = j;
                break :loop_top;
            }
        }
    }
    try stdout.print("x = {}, y = {}\n", .{ x, y });
}
実行結果:
c = 'l'(2, 3) 
x = 2, y = 3
Zigの関数は、プログラム内で再利用可能なコードブロックを表します。
Zigの関数の引数は、const で宣言された変数と同じくイミュータブルです。
ミュータブルにする方法はありません。
構文(EBNF):
top-level-decl = [ "inline" | "noinline" ] FnProto ( ";" | block )
FnProto = "fn" [ IDENTIFIER ] "(" param-decl-list ")" [ byte-align ] [ link-section ] [ call-conv ] [ "!" ] type-expr
param-decl-list = ( param-decl "," )* [ param-decl ]
param-decl = [ doc_comment ] [ "noalias" | "comptime" ] [ IDENTIFIER ":" ] param-type | "..."
param-type = "anytype" | type-expr
type-expr = [ prefix-type-op ] ErrorUnionExpr
prefix-type-op = "?"
             | "anyframe" "->"
             | slice-type-start ( byte-align | "const" | "volatile" | "allowzero" )*
             | ptr-type-start ( "align" "(" expr [ ":" INTEGER ":" INTEGER) ] ")"  | "const" | "volatile" | "allowzero" )*
             | array-type-start
slice-type-start = "[" [ ":" expr ] "]"
byte-align = "align" "(" expr ")"
link-section = "linksection" "(" expr ")"
call-conv = "callconv" "(" expr ")"
ptr-type-start = "*" | "**" | "[" "*" [ "c" | ":" expr ] "]"
array-type-start = "[" expr [ ":" expr ] "]"
top-level-declは関数宣言の他、変数宣言なども含みますが、関数宣言部分を抜粋しました。
[https://wandbox.org/permlink/S46Dr2HRn6xgzSSA 関数の例]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    try stdout.print("{}\n", .{div(1, 0)});
    try stdout.print("{}\n", .{div(0, 0)});
}
fn div(f1: f64, f2: f64) f64 {
    return f1 / f2;
}
実行結果:
inf
nan
他のプログラミング言語をご存じの方なら、関数 div() が前方参照になっているのは大丈夫なのか？と思われるかもしれません。
Zigではトップレベルの識別子は処理系が参照解決（とシグネチャーを含めた型の一致の確認）を引受けてくれます。
----
基本的な関数の定義: Zigでの関数の定義は、以下のように行います。
fn add(a: i8, b: i8) i8 {
    return a + b;
}
この例では、addという関数が定義されており、2つのi8型の引数を受け取り、i8型の値を返します。
外部関数の定義: Zigでは、外部ライブラリやAPIから関数を利用するために、externキーワードを使用して外部関数を宣言します。
extern "kernel32" fn ExitProcess(exit_code: u32) callconv(WINAPI) noreturn;
この例では、Windowsのkernel32ライブラリからExitProcessという関数を使用しています。
パラメータの型推論: 関数のパラメータの型は、anytypeを使用して関数が呼び出されるときに推論されます。
fn addFortyTwo(x: anytype) @TypeOf(x) {
    return x + 42;
}
この例では、addFortyTwo関数のパラメータxの型が呼び出し時に推論されます。
インライン関数: inlineキーワードを使用することで、関数をコールサイトにインライン展開することができます。
inline fn foo(a: i32, b: i32) i32 {
    return a + b;
}
 
この例では、foo関数がインラインで展開され、コンパイル時に計算が行われます。
関数ポインタ: 関数を値として扱うために、関数ポインタを使用することができます。
const Call2Op = *const fn (a: i8, b: i8) i8;
fn doOp(fnCall: Call2Op, op1: i8, op2: i8) i8 {
    return fnCall(op1, op2);
}
この例では、Call2Op型の関数ポインタを引数として受け取り、その関数を呼び出します。
Zigの関数は、柔軟性があり、さまざまな目的に使用できます。関数は、プログラムの構造化と再利用性を高めるために不可欠な要素です。
Zigでは、エラー( ''Errors'' )も１つの型です[https://ziglang.org/documentation/master/#Errors Errors]。
型( ''Error Set Type'' )は、enum のようなものです。同じエラー名を複数回宣言することは可能で、宣言した場合は同じ整数値が割り当てられます[https://ziglang.org/documentation/master/#Error-Set-Type Error Set Type]。
コンパイル全体のユニークなエラー値の数が、エラー集合型のサイズを決定するはずです。しかし、いま0.10.0は u16 になるようにハードコーディングされています。
サブセットからスーパーセットへエラーを強制することができます。
構文(EBNF):
error-set-decl = "error" "{" identifier-list "}"
エラー集合型の例:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const FileOpenError = error {
    AccessDenied,
    OutOfMemory,
    FileNotFound,
};
const AllocationError = error {
    OutOfMemory,
};
pub fn main() !void {
    try stdout.print("AllocationError.OutOfMemory == FileOpenError.OutOfMemory ⇒ {}\n", .{AllocationError.OutOfMemory == FileOpenError.OutOfMemory});
}
実行結果:
AllocationError.OutOfMemory == FileOpenError.OutOfMemory ⇒ true
エラー集合型をマージするには、|| 演算子を使用します。
結果として得られるエラー集合型には、両方のエラー集合型のエラーが含まれます。
''' は、グローバルエラー集合を参照します。これは、コンパイルユニット全体のすべてのエラーを含むエラー集合です。これは他のすべてのエラー集合のスーパーセットで、どのエラー集合のサブセットでもありません。
任意のエラー集合をグローバルエラー集合に強制することができ、グローバルエラー集合のエラーを非グローバルエラー集合に明示的にキャストすることができます。この場合、言語レベルのアサートが挿入され、エラー値が宛先のエラー集合に実際に含まれていることが確認されます。
グローバルエラー集合は、コンパイラーがコンパイル時にどのようなエラーが起こりうるかを知ることができないため、一般に避けるべきです。コンパイル時にエラー集合を知っていた方が、生成されるドキュメントや有用なエラーメッセージ（例えば switch で起こりうるエラー値を忘れてしまうなど）に有利です。
エラー集合型と正常型を二項演算子 ! で結合して、エラーユニオン型( ''Error Union Type'' )にすることができます。
var error_or_value: AllocationError ! u16 = 10;
エラーユニオン型は、エラー集合型単体よりも頻繁に使用される可能性があります[https://ziglang.org/documentation/master/#Error-Union-Type Error Union Type]。
構文(EBNF):
error-union-expr = suffix-expr ( "!" TypeExpr )?
suffix-expr = "async" PrimaryTypeExpr SuffixOp* FnCallArguments
            |       PrimaryTypeExpr ( SuffixOp | FnCallArguments )*
Zigには、演算子のオーバーロードはありません。
Zigのプログラムに演算子を見たとき、それが次の一覧表に示すもので、それ以外のものでないことが保証されます[https://ziglang.org/documentation/master/#Operators Operators]。
Zigでは、四則演算などの演算子はディフォルトでオーバーフローを検出します。それとは別に、（C言語などのように）ラッピングを行う演算子が別に用意されています。
オーバーフローの例:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var r :i3 = 0;
    while (r < 10) : (r += 1){
        try stdout.print("{} ", .{r});
    }
}
実行結果:
An error occurred: 
0 1 2 3 thread 7015 panic: integer overflow 
/tmp/playground2097195080/play.zig:6:25: 0x22cff6 in main (play) 
   while (r < 10) : (r += 1){ 
                       ^ 
/usr/local/bin/lib/std/start.zig:561:37: 0x22657a in std.start.callMain (play) 
           const result = root.main() catch |err| { 
                                   ^ 
/usr/local/bin/lib/std/start.zig:495:12: 0x20716e in std.start.callMainWithArgs (play) 
   return @call(.{ .modifier = .always_inline }, callMain, .{}); 
          ^ 
/usr/local/bin/lib/std/start.zig:409:17: 0x206206 in std.start.posixCallMainAndExit (play) 
   std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp })); 
               ^ 
/usr/local/bin/lib/std/start.zig:322:5: 0x206012 in std.start._start (play) 
   @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{}); 
   ^
符号付き３ビット整数の r を r < 10 の間インクリメントしています。
符号付き３ビット整数は、 -4 ... 3 の値しか表せず 4 になるとオーバーフローします。
C言語などでは、無限ループになりますが Zig ではランタイムに検出されます。
Zigには、四則演算などの演算子に飽和演算( ''Saturation calculation'' )バージョンが用意されています。
飽和演算の例:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var i: i5 = 0;
    var sum: i5 = 0;
    while (i < 10) : (i += 1) {
        sum +|= i;
        try stdout.print("i = {}, sum = {}\n", .{ i, sum });
    }
}
実行結果:
i = 0, sum = 0 
i = 1, sum = 1 
i = 2, sum = 3 
i = 3, sum = 6 
i = 4, sum = 10 
i = 5, sum = 15 
i = 6, sum = 15 
i = 7, sum = 15 
i = 8, sum = 15 
i = 9, sum = 15
高
 x() x[] x.y x.* x.?
 a!b
 x{}
 !x -x -%x ~x &x ?x
 * / % ** *% *| ||
 + - ++ +% -% +| -|
 << >> <<|
 & ^ | orelse catch
 and
 or
低
Zigには「オプショナル型」( ''optional type'' )と「非オプショナル型」があります。
オプショナル型: 値としてnullを受け入れられる。
非オプショナル型:  値としてnullを受け入れられない。
ここまでに紹介した変数および定数の型は非オプショナル型です。
オプショナル型の変数および定数は宣言のときに ?型名 と書きます。
var a: i32 = null;  // 非オプショナル型を null で初期化するとエラーになる！
var b: ?i32 = null; // オプショナル型は null で初期化してもエラーにならない
オプショナル型の式から基底型の値を参照することをアンラップを呼びます。
[https://wandbox.org/permlink/5JjpPVjhPwt8dqhb ifを使ったアンラップ]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var s: ?[]const u8 = null;
    if (s) |ss| {
        try stdout.print("{s}\n", .{ss});
    } else {
        try stdout.writeAll("it's null\n");
    }
    s = "abc";
    if (s) |ss| {
        try stdout.print("{s}\n", .{ss});
    } else {
        try stdout.writeAll("it's null\n");
    }
}
実行結果:
it's null
abc
Zigの配列は、次のような特徴を持ちます：
配列の定義
配列の操作
コンパイル時初期化
多次元配列
センチネル終端配列
Zigの配列は柔軟で効率的なデータ構造であり、様々なアプリケーションで使用されます。
構文(EBNF):
array-type-start = "[" expr [ ":" expr ] "]"
配列には、特有な演算子が２つあります。
配列のコード例:
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    const ary = [_]i8{ 2, 3, 5, 7 };
    try stdout.print("ary == {}{{ ", .{@TypeOf(ary)});
    for (ary) |elm| {
        try stdout.print("{}, ", .{elm});
    }
    try stdout.writeAll("}\n");
    try stdout.print("ary ++ ary == {}{{ ", .{@TypeOf(ary ++ ary)});
    for (ary ++ ary) |elm| {
        try stdout.print("{}, ", .{elm});
    }
    try stdout.writeAll("}\n");
    try stdout.print("ary ** 3 == {}{{ ", .{@TypeOf(ary ** 3)});
    for (ary ** 3) |elm| {
        try stdout.print("{}, ", .{elm});
    }
    try stdout.writeAll("}\n");
}
実行結果:
ary == [4]i8{ 2, 3, 5, 7, } 
ary ++ ary == [8]i8{ 2, 3, 5, 7, 2, 3, 5, 7, } 
ary ** 3 == [12]i8{ 2, 3, 5, 7, 2, 3, 5, 7, 2, 3, 5, 7, }
const ary = [_]i8{ 2, 3, 5, 7 };
const ary: [4]i8= [4]i8{ 2, 3, 5, 7 };
ですが、型は型推定で、要素数もリテラルなので _ と書けます。
ary ++ ary
++ は配列と配列を連結した新しい配列を返す演算子です。
ary ** 3
** は配列を回数だけ繰返した新しい配列を返す演算子です。
ary ** ary は ary ++ ary ++ ary と等価です。
多次元配列( ''Multidimensional Arrays'' )は、ネストした配列で生成します[https://ziglang.org/documentation/master/#Multidimensional-Arrays Multidimensional Arrays]。
多次元配列:
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    var matrix: [16][16]i32 = undefined;
    // 全成分を 0 に
    for (&matrix) |*col| {
        for (&col.*) |*value| {
                value.* = 0;
        }
    }
    try stdout.print("martix == {}{{\n", .{@TypeOf(matrix)});
    for (matrix) |col| {
        try stdout.writeAll("  { ");
        for (col) |value| {
            try stdout.print("{}, ", .{value});
        }
        try stdout.writeAll("},\n");
    }
    try stdout.writeAll("}\n\n");
    // 対角成分を 1 に
    for (&matrix, 0..) |*col, ci| {
        for (&col.*, 0..) |*value, ri| {
            if (ci == ri) {
                value.* = 1;
            }
        }
    }
    try stdout.print("martix == {}{{\n", .{@TypeOf(matrix)});
    for (matrix) |col| {
        try stdout.writeAll("  { ");
        for (col) |value| {
            try stdout.print("{}, ", .{value});
        }
        try stdout.writeAll("},\n");
    }
    try stdout.writeAll("}\n");
}
実行結果:
martix == [16][16]i32{
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
}
martix == [16][16]i32{
  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
}
    var matrix : [16][16]i32 = undefined;
undefined で初期化しています。というより初期化していません。
    var matrix : [16][16]i32 = 0; // これはエラーになる
と書きたいところですができないので、二重のforループで初期化しています。
センチネル終端配列( ''Sentinel Terminated Arrays'' )は、文字列のように特殊な値（センチネル（ ''sentinel''；番兵 ））で終端した配列です。
文字列の場合のセンチネルは ’\0’ ですが、構文 [配列長:x]要素型 の x で任意の値をセンチネルにできます[https://ziglang.org/documentation/master/#Sentinel-Terminated-Arrays Sentinel Terminated Arrays]。
センチネル終端配列:
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    const ary = [_:-1]i32{ 1, 4, 9, 16, 25 };
    try stdout.print("@TypeOf(ary) ⇒ {}\n", .{@TypeOf(ary)});
    var i: u16 = 0;
    while (i <= ary.len) : (i += 1) {
        try stdout.print("{} ", .{ary[i]});
    }
}
実行結果:
@TypeOf(ary) ⇒ [5:-1]i32 
1 4 9 16 25 -1 
ベクトルは、bool、整数、浮動小数点数、ポインターのグループで、可能であればSIMD命令を使って並列に操作されます。ベクトル型は、組込み関数 @Vector で作成します[https://ziglang.org/documentation/master/#Vectors Vectors]。
ベクトルは、基本型と同じ組み込み演算子をサポートしています。これらの演算は要素ごとに行われ、入力ベクトルと同じ長さのベクトルを返します。これには以下が含まれます。
算術演算:+, -, /, *, @divFloor, @sqrt, @ceil, @log, など
ビット演算子:>>, <<, &, |, ~, など
比較演算子:<, >, ==, など
スカラー（個々の数値）とベクトルが混在している場合に数学演算子を使用することは禁止されています。Zigでは、スカラーからベクトルへの変換を容易にするために組込み関数@splatが用意されており、ベクトルからスカラーへの変換には組込み関数@reduceと配列インデックスの構文がサポートされています。ベクトルは、長さが既知の固定長の配列との間の代入もサポートしています。
Zigは，組込み関数@shuffleと組込み関数@selectを提供し，ベクトル内やベクトル間の要素の並べ替えを行います．
ターゲットマシンのネイティブ SIMD サイズより短いベクトルに対する操作は、通常は単一の SIMD 命令にコンパイルされます。ある演算がターゲット・アーキテクチャで SIMD をサポートしていない場合、コンパイラーはデフォルトで各ベクトル要素に対して一度に 1 つずつ演算します。Zig は、232-1 までの既知のベクトル長をサポートしていますが、2 の累乗（2-64）が最も一般的です。ただし、現在の Zig では、長すぎるベクトル長 (例えば 220) はコンパイラーがクラッシュする可能性があります。
ベクトル演算:
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    const a = @Vector(4, i32){ 1, 2, 3, 4 };
    const b = @Vector(4, i32){ 5, 6, 7, 8 };
    try stdout.print("  {}\n+ {}\n= {}\n\n", .{ a, b, a + b });
    try stdout.print("  {}\n- {}\n= {}\n\n", .{ a, b, a - b });
    try stdout.print("  {}\n* {}\n= {}\n\n", .{ a, b, a * b });
    try stdout.print("  {}\n/ {}\n= {}\n\n", .{ a, b, a / b });
    try stdout.print("  {}\n% {}\n= {}\n\n", .{ a, b, a % b });
}
実行結果:
  { 1, 2, 3, 4 } 
+ { 5, 6, 7, 8 } 
= { 6, 8, 10, 12 }
  
  { 1, 2, 3, 4 }
- { 5, 6, 7, 8 } 
= { -4, -4, -4, -4 }
  { 1, 2, 3, 4 } 
= { 5, 12, 21, 32 }
  { 1, 2, 3, 4 } 
/ { 5, 6, 7, 8 } 
= { 0, 0, 0, 0 } 
  { 1, 2, 3, 4 } 
% { 5, 6, 7, 8 } 
= { 1, 2, 3, 4 }
[TODO:配列との相互変換]
Zigには、単一項目と多項目の2種類のポインターがあります[https://ziglang.org/documentation/master/#Pointers Pointers]。
*T - 正確に1つの項目への単一項目ポインター。
[*]T - 未知数のアイテムへの多項目ポインター。
T は既知のサイズでなければならず、anyopaqueや他の opaque型 にはできません。
これらの型は、配列やスライスと密接に関係しています。
*[N]T - N 個のアイテムへのポインター、配列への単項目ポインターと同じです。
[]T - スライス (ファットポインター。[*]T型のポインターと長さを含みます）。
単項目ポインターを得るには、&xを使用します。
[TODO]
ロードとストアは、副作用がないことが前提です。MMIO (Memory Mapped Input/Output) のように、ロードやストアが副作用を持つべき場合は volatile を使用します[https://ziglang.org/documentation/master/#volatile volatile]。
それぞれの型にはアライメント( ''Alignment'' )があり、その型の値がメモリーからロードされたり、メモリーにストアされたりするとき、メモリーアドレスがこの数で均等に割り切れるようなバイト数になっています。この値は組込み関数 @alignOf を使って知ることができます[https://ziglang.org/documentation/master/#Alignment Alignment]。
アラインメントはCPUアーキテクチャに依存しますが、常に2のべき乗であり、1 << 29 より小さい値です。
Zigでは、ポインター型はアライメント値を持っています。この値が基礎となる型のアライメントと等しい場合、その型は省略することができます。
align は、ポインターのアライメントを指定するために使用します。また、変数や関数の宣言の後に使用して、その変数や関数へのポインターのアライメントを指定することができます。
allowzeroポインター属性は、ポインターのアドレスがゼロであることを許可します。これは、アドレスゼロがマッピング可能な独立型OSターゲットでのみ必要とされます。nullポインターを表現したい場合は、代わりにオプショナルポインターを使用します。allowzeroを持つオプショナルポインターは、ポインターと同じサイズではありません[https://ziglang.org/documentation/master/#allowzero allowzero]。
constポインター属性は、（ポインターではなく）ポインターが参照する値が変更できないことを示します。
センチネル終端ポインター( ''Sentinel Terminated Pointers'' )。
構文 [*:x]T は、センチネル値によって長さが決定されるポインターを記述します。これにより、バッファオーバーフローやオーバーリードから保護されます[https://ziglang.org/documentation/master/#Sentinel-Terminated-Pointers Sentinel Terminated Pointers]。
スライス( ''Slices'' )はポインターと長さです。配列とスライスの違いは、配列の長さが型の一部でコンパイル時にわかるのに対して、スライスの長さは実行時にわかることです。どちらも len プロパティでアクセスすることができます[https://ziglang.org/documentation/master/#Slices Slices]。
スライスを使ったコード例:
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    var array = [_]i32{ 2, 3, 5, 7, 11, 13, 17 };
    var q: usize = 1;
    const slice = array[q .. array.len -1];
    try stdout.print("@TypeOf(slice) ⇒ {}\n", .{@TypeOf(slice)});
    for (slice) |x| {
        try stdout.print("{} ", .{x});
    }
}
実行結果:
@TypeOf(slice) ⇒ []i32 
3 5 7 11 13 
センチネル終端スライス( ''Sentinel-Terminated Slices'' )。
構文[:x]Tは、実行時に既知の長さを持ち、また長さでインデックスされた要素で センチネル値を保証するスライスです。この型は、それ以前にセンチネル要素がないことを保証するものではありません。センチネル終端スライスはlenインデックスへのエレメントアクセスを可能にします[https://ziglang.org/documentation/master/#Sentinel-Terminated-Slices Sentine -Terminated Slices]。
スライスを使ったコード例:
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    const slice: [:0]const u8 = "hello";
    try stdout.print("@TypeOf(slice) ⇒ {}\n", .{@TypeOf(slice)});
    for (slice) |ch| {
        try stdout.print("{c} ", .{ch});
    }
}
実行結果:
@TypeOf(slice) ⇒ [:0]const u8 
h e l l o 
コンテナー（Containers）とは、変数や関数の宣言を保持する名前空間として機能する構文上の構造です。コンテナーは、インスタンス化可能な型定義でもあります。struct、enum、unionとopaque そしてZigのソースファイル自体も、コンテナーの例です。
コンテナーは、定義を囲むために波括弧を使用しますが、ブロックや関数とは異なります。コンテナーには文が含まれていません。
[TODO:std.containerについて]
構文(EBNF):
container-members = container-declarations ( container-field "," )* (container-field | container-declarations)
container-declarations = TestDecl container-declarations   
                      | TopLevelComptime container-declarations
                      | [ doc_comment ] [ "pub" ] TopLevelDecl container-declarations
                      | 
container-field = [ doc_comment ] [ "comptime" ] IDENTIFIER [ ":" ( "anytype" | TypeExpr)  [ ByteAlign ] ] [ "=" expr ]
container-decl-auto = container-decl-type "{" [ container_doc_comment ] container-members "}"
container-decl-type = "struct" 
                  | "opaque" 
                  | "enum" [ "(" expr ")" ]
                  | "union" [ "(" ( "enum" [ "(" expr ")" ] ) | expr  ")" 
struct（構造あるいは構造型C言語の struct は、テンプレートの定義を行う構文で型にするためには、追加の typedef が必要で「構造体」がふさわしかったのですが、Zig では正確に型を定義するので「構造」あるいは「構造型」が妥当です。ここでは混乱を避けるためキーワードでもある struct としました。）はデータの集合を定義するための構文であり、フィールドにはデフォルト値やメソッドを持たせることができます。packed structやextern structといった特殊な形式も提供され、メモリレイアウトを制御します。関数内でのstructの生成やジェネリックな構造体の定義もサポートされています。さらに、関数からstructを返すことも可能です。
struct の定義:
const stdout = @import("std").io.getStdOut().writer();
const Complex = struct {
    real: f64,
    imag: f64,
};
pub fn main() !void {
    try stdout.print("@TypeOf(Complex) ⇒ {}\n", .{@TypeOf(Complex)});
}
実行結果:
@TypeOf(Complex) ⇒ type
3-6 行目が struct の宣言で、変数に保存しています。
struct で宣言した値の型は type です。
ところで Zigはフィールドの順番とサイズを保証しません。しかし、フィールドはABIアラインされていることが保証されています[https://ziglang.org/documentation/master/#struct struct]。
もし、順番とサイズを保証したい場合キーワード packed を前置します。
順番とサイズを保証したい場合:
const Complex = packed struct {
    real: f64,
    imag: f64,
};
インスタンスの生成:
const stdout = @import("std").io.getStdOut().writer();
const Complex = struct {
    real: f64,
    imag: f64,
};
pub fn main() !void {
    const cplx = Complex{
        .real = 1.2,
        .imag = 4.1,
    };
    try stdout.print("{}\n", .{ cplx });
}
実行結果:
Complex{ .real = 1.2e+00, .imag = 4.1e+00 }
メンバー名の前に . が付くのが独特です。
メンバーは常に pub です。
private や protected や friend はありません。
struct はメソッド( ''Methods'' )を持つことができます。
メソッドは特別なものではなく、名前空間を持つだけです。
ドットシンタックス（インスタンス名.メソッド名）で呼び出すことができる関数です。
メソッドの例:
const stdout = @import("std").io.getStdOut().writer();
const sqrt = @import("std").math.sqrt;
const Complex = struct {
    real: f64,
    imag: f64,
    pub fn init(real: f64, imag: f64) Complex {
        return Complex{
            .real = real,
            .imag = imag,
        };
    }
    pub fn abs(self: Complex) f64 {
        return sqrt(self.real * self.real + self.imag * self.imag);
    }
};
pub fn main() !void {
    const cplx = Complex.init(3.3, 4.4);
    try stdout.print("cplx = {}\n", .{cplx});
    try stdout.print("cplx.abs() = {}\n", .{cplx.abs()});
    try stdout.print("Complex.abs(cplx) = {}\n", .{Complex.abs(cplx)});
}
実行結果:
cplx = Complex{ .real = 3.3e+00, .imag = 4.4e+00 } 
cplx.abs() = 5.5e+00 
Complex.abs(cplx) = 5.5e+00
インスタンスを生成するメソッド init() と絶対値を返すメソッド abs() を定義しました。
インスタンスを生成するメソッドに init と名付けるのも、フィールドにアクセスするメソッドの第一引数を self と名付けるのも言語仕様ではなく慣習ですが、逸脱する積極的な理由はありません。
[TODO:組込み関数＠This]
Zigでは、structリテラルの型は識別子と結びついている必要はありません。結果が強制される場合、structリテラルはコピーなしで結果の場所を直接インスタンス化します。
( ''Tuple'' )と呼びます[https://ziglang.org/documentation/master/#Anonymous-Struct-Literals Anonymous Struct Literals]。
Zigのenumは列挙型を定義するための構文であり、異なる値をグループ化します[https://ziglang.org/documentation/master/#enum enum]。
値の順序を指定せずに列挙型を宣言することができ、任意の整数型を指定してタグ型を制御できます。
タグ型を指定しない場合、自動的に整数型が割り当てられ、0から始まる連続した値が与えられます。
列挙型にはメソッドを追加することができ、switch文を使用して値を切り替えることができます。
また、列挙型リテラルを使用して、特定の列挙値を指定することも可能です。
Zigのenumは柔軟性が高く、CのABIとの互換性を保つための機能も提供されています。
enum の定義:
const stdout = @import("std").io.getStdOut().writer();
const Colour = enum {
    red,
    green,
    blue,
};
const JISC5062 = enum(u4) {
    black = 0,
    brown,
    red,
    orange,
    yellow,
    green,
    blue,
    violet,
    gray,
    white,
    const Self = @This();
    const len = @typeInfo(Self).Enum.fields.len;
    pub fn allCases() [Self.len]Self {
        var result: [Self.len]Self = undefined;
        var i: u4 = 0;
        while (i < Self.len) : (i += 1) {
            result[i] = @as(Self, @enumFromInt(i));
        }
        return result;
    }
};
pub fn main() !void {
    try stdout.print("@TypeOf(Colour) ⇒ {}\n", .{@TypeOf(Colour)});
    const c: Colour = .green;
    try stdout.print("c ⇒ {}\n", .{c});
    try stdout.print("JISC5062.allCases() ⇒ {any}\n", .{JISC5062.allCases()});
    for (JISC5062.allCases()) |e| {
        try stdout.print("{s} ⇒ {}\n", .{ @tagName(e), @intFromEnum(e) });
    }
}
実行結果:
@TypeOf(Colour) ⇒ type
c ⇒ main.Colour.green
JISC5062.allCases() ⇒ { main.JISC5062.black, main.JISC5062.brown, main.JISC5062.red, main.JISC5062.orange, main.JISC5062.yellow, main.JISC5062.green, main.JISC5062.blue, main.JISC5062.violet, main.JISC5062.gray, main.JISC5062.white }
black ⇒ 0
brown ⇒ 1
red ⇒ 2
orange ⇒ 3
yellow ⇒ 4
green ⇒ 5
blue ⇒ 6
violet ⇒ 7
gray ⇒ 8
white ⇒ 9
3-7,9-31行目が enum の宣言で、それぞれ変数に保存しています。
enum で宣言した値の型は type です。
enum は、struct と同じくメンバーとは別に、型に属する変数やメソッドを持つことが出来ます。
JISC5062.allCases() は、JISC5062のメンバー全てを含む配列を返しています。
const Self = @This();は、コンテナー定義でコンテナーの識別子を書く手間をなくし、コピーアンドペーストを容易にします。
red, green と blue が Colour と JISC5062 で重複していますが、コンテナーは名前空間として機能するので問題になりません。
const std = @import("std");
const mem = std.mem;
const testing = std.testing;
const expect = testing.expect;
test "Enum w/ method" {
    // Enumを定義します。
    const Color = enum {
        red,
        green,
        blue,
    };
    // Enumフィールドを宣言します。
    const primaryColor: Color = .red;
    _ = primaryColor;
    // Enumメソッドを持たせます。
    const Suit = enum {
        clubs,
        spades,
        diamonds,
        hearts,
        const Self = @This();
        pub fn isClubs(self: Self) bool {
            return self == Self.clubs;
        }
    };
    // Enumメソッドをテストします。
    const p = Suit.spades;
    try expect(!p.isClubs());
}
test "Enum w/ switch" {
    // Enumをスイッチで使用します。
    const Foo = enum {
        string,
        number,
        none,
    };
    const p2 = Foo.number;
    const what_is_it = switch (p2) {
        Foo.string => "This is a string",
        Foo.number => "This is a number",
        Foo.none => "This is none",
    };
    try expect(mem.orderZ(u8, what_is_it, "This is a number") == .eq);
}
test "Enum Literal " {
    // Enumリテラルを使用します。
    const Status = enum {
        ok,
        err,
    };
    const status1: Status = .ok;
    const status2 = Status.err;
    try expect(status1 != status2);
}
test "Non-exhaustive enum" {
    // 非全網羅Enumを作成します。
    const Shape = enum(u8) {
        circle,
        square,
        rectangle,
        _,
    };
    const shape = Shape.circle;
    const shapeMessage = switch (shape) {
        Shape.circle => "This is a circle",
        Shape.square => "This is a square",
        Shape.rectangle => "This is a rectangle",
        _ => "Unknown shape", // 非全網羅のため、_ で処理
    };
    try expect(mem.orderZ(u8, shapeMessage, "This is a circle") == .eq);
}
Zigのunionは、値が取りうる可能な型のセットをフィールドのリストとして定義しますす[https://ziglang.org/documentation/master/#union union]。
一度に1つのフィールドしかアクティブにできません。
裸のUnionのメモリ表現は保証されておらず、メモリを再解釈するためには@ptrCastを使用するか、保証されたメモリレイアウトを持つextern unionまたはpacked unionを使用する必要があります。
非アクティブなフィールドへのアクセスは安全性が確認されておらず、未定義の動作になります。
Union全体を割り当てることで他のフィールドをアクティブにできます。
また、Switch文を使用するためには、Unionにenumタグ型を付ける必要があります。
これにより、Switch式でUnionのペイロードを変更することが可能になります。
Unionにはenumタグ型を推論させることもでき、またstructやenumと同様にメソッドを持つことができます。
[https://wandbox.org/permlink/ySVa32mXWe7uzLPF 非アクティブフィールドを参照するとコンパイルエラーになります]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const SimpleUnion = union {
    char: u8,
    int: i64,
    float: f64,
};
pub fn main() !void {
    var su = SimpleUnion{ .char = 'C' };
    try stdout.print("su.int = {}\n", .{su.int});
}
コンパイル結果:
panic: access of inactive union field
[https://wandbox.org/permlink/5Z9kPxAAFDKJsGFG アクティブフィールドであれば参照できます]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const SimpleUnion = union {
    char: u8,
    int: i64,
    float: f64,
};
pub fn main() !void {
    var su = SimpleUnion{ .char = 'C' };
    try stdout.print("su.char = {c}\n", .{su.char});
    su = SimpleUnion{ .int = 42 };
    try stdout.print("su.int = {}\n", .{su.int});
    su = SimpleUnion{ .float = 2.71828_18284_59045_23536_02874_71352 };
    try stdout.print("su.float = {}\n", .{su.float});
}
実行結果:
su.char = C
su.int = 42 
su.float = 2.718281828459045e+00
'' )になり、switch式で使用することができるようになります。タグ付きunionは、そのタグ型に強制されます[https://ziglang.org/documentation/master/#Tagged-union Tagged union]。
[https://wandbox.org/permlink/WnsPOu50oZMhJNxh タグ付きunion]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const Tag = enum {
    char,
    int,
    float,
};
const TaggedUnion = union(Tag) {
    char: u8,
    int: i64,
    float: f64,
};
pub fn main() !void {
    var tu = TaggedUnion{ .char = 'C' };
    try stdout.print("@as(Tag, tu) ⇒ {}\n", .{@as(Tag, tu)});
    switch (tu) {
        Tag.char => |ch| try stdout.print("ch = {c}\n", .{ch}),
        Tag.int => |i| try stdout.print("i = {}\n", .{i}),
        Tag.float => |f| try stdout.print("f = {}\n", .{f}),
    }
}
実行結果:
@as(Tag, tu) ⇒ Tag.char 
ch = C
タグの enum と、それを使うタグ付きunionのメンバー集合は一致していないとエラーになります。
タグ付きunionを式とするswitch式では、バターンでタグのメンバーを網羅している必要があります（網羅性の検証が行えます。ただし _ や else をパターン使うと台無し）。
[https://wandbox.org/permlink/dvve8enu4S5GZQH3 匿名enum版タグ付きunion]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const TaggedUnion = union(enum) {
    char: u8,
    int: i64,
    float: f64,
};
pub fn main() !void {
    var tu = TaggedUnion{ .char = 'C' };
    try stdout.print("@tagName(tu) ⇒ {s}\n", .{@tagName(tu)});
    switch (tu) {
        .char => |ch| try stdout.print("ch = {c}\n", .{ch}),
        .int => |i| try stdout.print("i = {}\n", .{i}),
        .float => |f| try stdout.print("f = {}\n", .{f}),
    }
}
匿名enum版タグ付きunionは、enum のメンバーと union のメンバーの名前を一致させる手間が不要です。
他方、（unionのメンバーでなく）enum のメンバーに値を与えたいときは、実体化したタグをつか合う必要があります。
構文木を考えてみましょう。Cで実装するとノードの種別と種別ごとのペイロードの共用体になります。Zigではこれを一般化してタグ付きunion１つで実装することが出来ます。
タグ付きunionを使うと網羅性の保証もでき、多くの特性をメソッドとして記述できます。たとえばキーワードや演算子を追加した場合、対応するswitchのパターンがないとエラーになりコンパイル時に事前に変更必要箇所を確認できます。
switch式でタグ付きunionのペイロードを変更するには、変数名の前に*を置き、ポインターにします
[https://wandbox.org/permlink/5MMXzHDF0O9FkHro switch式でタグ付きunionの値の変更]:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const Tag = enum {
    char,
    int,
    float,
};
const TaggedUnion = union(Tag) {
    char: u8,
    int: i64,
    float: f64,
};
pub fn main() !void {
    var tu = TaggedUnion{ .int = 42 };
    try stdout.print("@as(Tag, tu) ⇒ {}\n", .{@as(Tag, tu)});
    switch (tu) {
        Tag.char => |*ch| ch.* -= 1,
        Tag.int => |*i| i.* += 10,
        Tag.float => |*f| f.* /= 1.2,
    }
    try stdout.print("tu.int ⇒ {}\n", .{tu.int});
}
実行結果:
@as(Tag, tu) ⇒ Tag.int 
tu.int ⇒ 52
union も他のコンテナーと同様にメソッドを持つことが出来ます。
unionのメソッド:
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const signbit = std.math.signbit;
const Number = union(enum) {
    Int: i32,
    Float: f32,
    const Self = @This();
    // 共用体にメソッドを追加
    pub fn isPositive(self: Self) bool {
        return switch (self) {
            .Int => |value| value > 0,
            .Float => |value| !signbit(value),
        };
    }
};
pub fn main() !void {
    const i = Number{ .Int = 42 };
    const f = Number{ .Float = -4.82 };
    try stdout.print("i.isPositive() = {}\n", .{i.isPositive()});
    try stdout.print("f.isPositive() = {}\n", .{f.isPositive()}); 
}
実行結果:
i.isPositive() = true
f.isPositive() = false
このコードは、ZigのUnionにメソッドを追加し、そのメソッドを使用して共用体の値を操作する方法を示しています。また、浮動小数点数の場合には、std.math.signbit を使用して符号を判定しています。
まず、Number というUnionが定義されています。このUnionは、Int フィールドと Float フィールドを持ち、それぞれ整数型と浮動小数点数型の値を保持します。また、const Self = @This() を使用して、メソッド内で共用体の型を参照できるようにしています。
次に、共用体に isPositive メソッドが追加されています。このメソッドは、共用体が保持する値が正の値かどうかを判定します。整数型の場合は、単純に値が0より大きいかどうかを確認しています。浮動小数点数型の場合は、std.math.signbit を使用して、値の符号を判定しています。符号が正の場合は、値が正の数であると判断します。
最後に、main 関数では、整数と浮動小数点数の共用体を作成し、それぞれの isPositive メソッドを呼び出して、共用体が保持する値が正の値かどうかを確認しています。結果は標準出力に出力されます。
実行結果では、整数が正の値であり、浮動小数点数が負の値であるため、"i.isPositive() = true" と "f.isPositive() = false" というメッセージが表示されます。
const std = @import("std");
const testing = std.testing;
const expect = testing.expect;
// Bare Unionの定義
const Payload = union {
    int: i64,
    float: f64,
    boolean: bool,
};
// Bare Unionの使用例
test "Bare Union" {
    var payload = Payload{ .int = 1234 };
    try expect(payload.int == 1234);
}
// Tagged Unionの定義
const ComplexTypeTag = enum {
    ok,
    not_ok,
};
const ComplexType = union(ComplexTypeTag) {
    ok: u8,
    not_ok: void,
};
// Tagged Unionの使用例
test "Tagged Union" {
    const c = ComplexType{ .ok = 42 };
    try expect(@as(ComplexTypeTag, c) == ComplexTypeTag.ok);
    try switch (c) {
        ComplexTypeTag.ok => |value| expect(value == 42),
        ComplexTypeTag.not_ok => unreachable,
    };
}
// Unionにメソッドを追加する
const Variant = union(enum) {
    int: i32,
    boolean: bool,
    none,
    fn truthy(self: Variant) bool {
        return switch (self) {
            Variant.int => |x_int| x_int != 0,
            Variant.boolean => |x_bool| x_bool,
            Variant.none => false,
        };
    }
};
// Unionのメソッドの使用例
test "Union Method" {
    var v1 = Variant{ .int = 1 };
    var v2 = Variant{ .boolean = false };
    try expect(v1.truthy());
    try expect(!v2.truthy());
}
// @tagNameを使用したEnumの値の表示
const Small2 = union(enum) {
    a: i32,
    b: bool,
    c: u8,
};
// @tagNameの使用例
test "@tagName" {
    try expect(std.mem.eql(u8, @tagName(Small2.a), "a"));
}
// 匿名Unionリテラルの初期化
const Number = union {
    int: i32,
    float: f64,
};
// 匿名Unionリテラルの使用例
test "Anonymous Union Literal Syntax" {
    const i: Number = .{ .int = 42 };
    const f = makeNumber();
    try expect(i.int == 42);
    try expect(f.float == 12.34);
}
fn makeNumber() Number {
    return .{ .float = 12.34 }; 
}
実行結果:
1/5 test.Bare Union... OK 
2/5 test.Tagged Union... OK 
3/5 test.Union Method... OK 
4/5 test.@tagName... OK 
5/5 test.Anonymous Union Literal Syntax... OK 
All 5 tests passed.
Zigのopaqueは、サイズとアライメントが不明（ただしゼロではない）な新しい型を宣言します。struct、union、enumと同様に、宣言を含めることができます。
これは、構造の詳細を公開しないCコードとやり取りする際の型安全性のために一般的に使用されます。例えば：
const Derp = opaque {};
const Wat = opaque {};
extern fn bar(d: *Derp) void;
fn foo(w: *Wat) callconv(.C) void {
    bar(w);
}
test "call foo" {
    foo(undefined);
}
opaqueを使うと、foo関数内でbar関数を呼び出す際に型の安全性が保証されます。
ブロック（Blocks）は、プログラム内で複数の文をグループ化する構造で、主に変数のスコープを制限し、可読性を向上させます。ブロック内で宣言された変数は、そのブロックの外部からはアクセスできず、名前の衝突を防ぎます。また、条件分岐やループの本体として使用され、制御フローを明確にします。さらに、ラベル付きブロックではbreak文を使って値を返したり、ブロックから脱出したりすることができます。空のブロックはプログラムの構造を整理するために使用され、有効な構造化要素として機能します。ブロックはコードの構造化に不可欠であり、プログラムの理解と保守性を向上させます。
構文(EBNF):
block = "{" statement* "}"
statement = "comptime"? var-decl
          | "comptime" block-expr-statement
          | "nosuspend" block-expr-statement
          | "suspend" block-expr-statement
          | "defer" block-expr-statement
          | "errdefer" pay-load? block-expr-statement
          | if-statement
          | labeled-statement
          | switch-expr 
          | AssignExpr ";"
ブロックは式です。ラベルを付けると、break はブロックから値を返すために使うことができます。
const stdout = @import("std").io.getStdOut().writer();
pub fn main() !void {
    var y: i32 = 123;
    const x = blk: {
        y += 1;
        break :blk y + 2;
    }; 
    try stdout.print("x = {}, y = {}\n", .{x, y});
}
実行結果:
x = 126, y = 124
ラベル blk は、任意の識別子に置換えられますが、慣習として blk がよく用いられます。
識別子は、同じ名前を使用して他の識別子を "隠す"ことは決して許されません。
コンパイラーは、外部スコープで既に使われている識別子を、ブロック内で使うとエラーにします。
このため、Zigのコードを読むときには、その識別子が定義されたスコープ内では常に同じ意味であることを確認することができます。ただし、スコープが分かれている場合（入れ子関係にない場合）には同じ名前を使用することができます。
defer は、スコープを抜けるときに実行される式またはブロックを登録します[https://ziglang.org/documentation/master/#defer defer]。
複数の defer 文で登録された場合、登録された逆の順序に実行されます。
const std = @import("std");
const stdout = std.io.getStdOut().writer();
fn basic_example() !usize {
    var x: usize = 0;
    {
        defer x = 123;
        x = 0;
        try stdout.print("@small block: x = {}\n", .{x});
    }
    try stdout.print("@function block: x = {}\n", .{x});
    x = 5;
    return x;
}
fn multiple_example() !void {
    try stdout.writeAll("multiple_example(): ");
    defer {
        try stdout.writeAll("1 ");
    }
    defer {
        try stdout.writeAll("2 ");
    }
    if (false) {
        // defer 自身が一度も実行されない場合は、実行されません。
        defer {
            try stdout.writeAll("3 ");
        }
    }
}
pub fn main() !void {
    try stdout.print("basic_example()  = {}\n", .{basic_example()});
    multiple_example();
}
実行結果:
@small block: x = 0 
@function block: x = 123 
basic_example()  = 5 
multiple_example(): 2 1 
errdefer は、エラーが原因でスコープを抜けるときに実行される式またはブロックを登録します[https://ziglang.org/documentation/master/#errdefer errdefer]。
複数の errdefer 文で登録された場合、登録された逆の順序に実行されます。
errdefer は、スコープを抜ける原因となったエラーコードをキャプチャーできます。
    errdefer |err| {
        std.debug.print("the error is {s}\n", .{@errorName(err)}); 
    }
Zigでは、キャスティングは一つの型から別の型への明示的な変換を指します。この機能は、異なる型間でのデータの変換や操作を可能にし、プログラムの柔軟性を高めます。Zigにはさまざまな種類のキャストがあり、それぞれが特定の目的に使用されます[https://ziglang.org/documentation/master/#Casting Casting]。
以下では、Zigのキャスティングについて詳細に説明します。
Zigでは、@bitCast、@alignCast、@enumFromIntなどのビルトイン関数を使用して、明示的なキャストを実行します。
これらのキャストは、安全なものとそうでないものがあります。一部のキャストはランタイムでの動作を変更せず、他のキャストは言語レベルのアサーションを実行します。
たとえば、@intCastは整数型間での変換を行いますが、ビットの丸めは行いません。これに対して、@floatCastは浮動小数点数をより小さいサイズの浮動小数点数に変換しますが、精度が失われる可能性があります。
switch、if、while、forなどの文脈で複数の型が存在する場合、Zigはそれらの型を解決します。これにより、すべての型が変換可能な型が選択されます。
たとえば、switch文内でi8とi16の変数を追加すると、その結果の型はi16になります。これにより、型が一貫性を持ち、予測可能な動作が実現されます。
これらのキャスト機能は、Zigの柔軟性と安全性を向上させ、異なる型間でのデータの変換や操作を容易にします。
Type Coercionは、一つの型が期待される場面で、異なる型が提供された場合に発生します。
test_type_coercion.zigでは、変数宣言や関数呼び出し時に型変換が示されています。
同じランタイム表現を持つ値を、修飾子の厳密さを増やすためにキャストすることができます。
整数および浮動小数点の型が、それを表現できるより大きな型に自動的に変換されます。
浮動小数点数を整数にキャストすることは曖昧であり、コンパイラエラーが発生します。
スライス、配列、ポインタの間での相互変換が示されています。
オプショナル型のペイロードやnull値が、オプショナル型自体にキャストされることが示されています。
エラーコードやエラーセットがエラーユニオン型にキャストされることが示されています。
数値が宛先型で表現可能である場合にのみ、キャストが行われることが示されています。
タグ付きユニオンが列挙型に、そして列挙型がタグ付きユニオンにキャストされることが示されています。
同じ型のフィールドを持つタプルは、配列にキャストすることができます。
@bitCast、@alignCast、@enumFromIntなどのビルトイン関数を使用して明示的なキャストが行われます。
Peer Type Resolutionは、スイッチ式やif式などの複数のオペランド型が与えられた場合に使用されます。
これらの機能は、Zig言語において型の柔軟な操作と安全な変換を可能にし、コードの表現力を高めます。
Zigにおける型強制（Type Coercion）は、コンパイラが型の不一致を解消するために行う自動的な型変換のプロセスを指します。Zigでは、いくつかの場面で型強制が行われます。
変数の代入: 変数がある型で宣言されている場合、その変数に代入される値が宣言された型と異なる場合、Zigは型強制を行います。ただし、この変換は安全な場合に限ります。たとえば、整数型から浮動小数点数型への変換は安全ですが、逆の変換は損失が発生する可能性があるため、コンパイラは警告やエラーを生成することがあります。
関数呼び出し時の引数の型の一致: 関数が特定の型の引数を受け取る場合、関数呼び出し時に与えられた引数の型が完全に一致しない場合、Zigは適切な型に変換します。これにより、型の不一致によるエラーが回避されます。
演算子の使用: 演算子を使用する際に、オペランドの型が一致しない場合、Zigは適切な型に変換して演算を行います。たとえば、整数型と浮動小数点数型の演算を行う場合、整数型は浮動小数点数型に変換されます。
明示的な型変換: プログラマが明示的に型を変換したい場合、@asや@intCastなどの組み込み関数を使用して、型変換を行うことができます。これにより、プログラマが意図的に型変換を行うことができます。
Zigの型強制は、安全な変換のみを行うことを目指しており、潜在的なデータの損失や不正確な結果を防ぐために注意深く設計されています。
明示的キャスト（Explicit Casts）は、コンパイラに対して特定の型への明示的な変換を指示する手段です。これは、コンパイラによって自動的に処理される型強制とは異なり、プログラマが意図的に型の変換を指定する場合に使用されます。Zigでは、さまざまな種類の明示的キャストが提供されています。
以下は、Zigで使用できる主な明示的キャストの例です：
@bitCast: ビットレベルでの変換を行い、型のビット表現を維持します。このキャストは非常に低レベルであり、注意が必要です。
@alignCast: ポインタのアライメントを増やします。より厳格なアライメントが必要な場合に使用されます。
@enumFromInt: 整数値から列挙型の値を取得します。列挙型の値を整数値に変換する逆の操作は、自動的な型強制で行われます。
@errorFromInt: 整数値からエラーコードを取得します。
@errorCast: より小さなエラーセットに変換します。
@floatCast: より大きな浮動小数点数型から小さな浮動小数点数型に変換します。
@floatFromInt: 整数値から浮動小数点数値に変換します。
@intCast: 整数型間での変換を行います。
@intFromBool: 真偽値を整数値に変換します。
@intFromEnum: 列挙型のタグ値を整数値として取得します。
@intFromError: エラーコードを整数値として取得します。
@intFromFloat: 浮動小数点数値の整数部分を取得します。
@intFromPtr: ポインタのアドレスを整数値として取得します。
@ptrFromInt: 整数値をポインタのアドレスに変換します。
@ptrCast: ポインタ型間の変換を行います。
@truncate: 整数型間での変換を行い、ビットを切り捨てます。
これらの明示的なキャストは、プログラマがコンパイラに対して特定の型変換を指定する必要がある場合に使用されます。ただし、使用する際には注意が必要であり、不適切なキャストがプログラムの安全性や正確性に影響を与える可能性があるため、慎重に検討する必要があります。
Zigのピア型解決（Peer Type Resolution）は、複数のオペランドの型から、それらが共通して受け入れ可能な型を決定するプロセスです。このプロセスは、通常、switchやifなどの条件式、またはforやwhileなどのループの条件式で使用されます。ピア型解決は、これらの式で使用される型を決定するために、与えられた複数の型の間で最も適切な共通型を見つけることを目的としています[https://ziglang.org/documentation/master/#Peer-Type-Resolution Peer Type Resolution]。
具体的には、ピア型解決は以下の場面で発生します：
ピア型解決では、複数の型の間で共通の型を見つけるために、次のようなルールが適用されます：
整数の場合、最大のビット幅を持つ整数型が選択されます。
浮動小数点数の場合、最も精度の高い浮動小数点数型が選択されます。
スライスや配列などのコンテナ型の場合、要素の型が共通の場合に選択されます。
オプショナル型の場合、共通の基底型が選択されます。
ポインタ型の場合、共通のポインタ型が選択されます。
これにより、Zigのコンパイラは、異なる型を持つ複数のオペランドに対して最も適切な共通の型を決定し、型の整合性を確保します。
ゼロビット型( ''Zero Bit Types'' )、実際にはデータを保持しない型のことを指します。これらの型は、メモリ内の領域を占有しないため、サイズがゼロビットです[https://ziglang.org/documentation/master/#Zero-Bit-Types Zero Bit Types]。
整数のu0とi0。
len == 0 または 0 ビット型の要素型を持つ配列とベクトル。
タグが1つしかないenum。
すべてのフィールドがゼロビット型であるstruct。
ゼロビット型のフィールドを1つだけ持つunion。
Zigでは、ゼロビット型は主に型システムの柔軟性と型安全性を高めるために使用されます。これらの型は、実行時のメモリ使用量やパフォーマンスには影響しませんが、コードの明確さと保守性を向上させるのに役立ちます。
[TODO:所謂インラインアセンラ]
Zigでは、キーワード async を伴って関数またはメソッドを呼び出すと、その関数またはメソッドの処理を休止し再開することができます。
[https://wandbox.org/permlink/NqJoL0PdY3mm3EAh async.zig]:
const std = @import("std");
var frame: anyframe = undefined;
pub fn main() !void {
    try println("begin main");
    _ = async func();
    try println("resume func");
    resume frame;
    try println("end main");
}
fn func() !void {
    try println("begin func");
    frame = @frame();
    suspend {}
    try println("end func");
}
fn println(s: []const u8) !void {
    try std.io.getStdOut().writer().print("{s}\n",.{s});
}
実行結果:
begin main
begin func
resume func
end func
end main
Zigのunreachableは、制御フローが特定の位置に到達しないことを明示するために使用されます。具体的には、その部分に到達することがプログラムの不正な動作であることを表明します。
if (false) {
    unreachable;
}
Debug と ReleaseSafe モード、および zig test を使用する場合、unreachable は到達不能なコードに到達したメッセージとともに panic への呼び出しを出します[https://ziglang.org/documentation/master/#unreachable unreachable]。
ReleaseFast モードでは、オプティマイザーは到達不能なコードは決してヒットしないという仮定を使用して最適化を実行します。しかし、ReleaseFastモードでもzigテストはunreachableをpanicへの呼出しとして出力します。
unreachableの型はnoreturnです[https://ziglang.org/documentation/master/#At-Compile-Time At Compile Time]。
＠TypeOf(unreachable)はコンパイルに失敗します。
unreachable式はコンパイルエラーになるからです。
noreturnは、つぎ文の型です[https://ziglang.org/documentation/master/#noreturn noreturn]。
if節やswitchの分岐先( ''prongs'' )など、型を一緒に解決する場合、noreturn型は他のすべての型と互換性があります。
Zigのビルドモードは、プログラムをビルドする際の特定の設定とオプションの組み合わせを指定するものです。Zigには次の4つのビルドモードがあります。
それぞれのビルドモードは、異なる目的に対応しています。例えば、デバッグ中には実行時のパフォーマンスよりもデバッグしやすさや安全性が重要ですが、リリース時には実行時のパフォーマンスやバイナリサイズの最適化が優先されることがあります。
ビルドモードを指定するには、zig build-exeコマンドを使用します。例えば、ReleaseFastモードでプログラムをビルドするには、次のようにします。
$ zig build-exe example.zig -O ReleaseFast
各ビルドモードの特性は以下の通りです。
これらのビルドモードは、プロジェクトのニーズや要件に応じて適切なものを選択することが重要です。
Zigのメモリー管理の基礎について、重要なポイントを要約します。
メモリー管理の責任: Zig言語では、プログラマーがメモリー管理を行います。つまり、Zigにはランタイムがないため、リアルタイムソフトウェア、オペレーティングシステムカーネル、組み込みデバイス、低遅延サーバーなど、さまざまな環境でZigコードがシームレスに動作します。
アロケーターの使用: Zigでは、C言語とは異なり、デフォルトのアロケーターは提供されません。代わりに、アロケートが必要な関数は Allocator パラメーターを受け入れます。データ構造も同様に、初期化関数で Allocator パラメーターを受け入れます。
アロケーターの選択: どのアロケーターを使用するかは、さまざまな要因に依存します。ライブラリを作成する場合やlibcをリンクする場合、またはメモリー要件が既知の場合など、適切なアロケーターを選択するためのフローチャートが提供されています。
メモリーの場所: Zigでは、異なる種類のデータが異なる場所に配置されます。例えば、関数内のvar宣言はその関数のスタックフレームに配置されます。一方、グローバルレベルや構造体内のvar宣言はグローバルデータセクションに配置されます。
アロケーターの実装: Zigプログラマーは、Allocator インターフェースを満たすことで独自のアロケーターを実装できます。これには、allocFnとresizeFnを提供する必要があります。
再帰: 再帰は、Zigでモデリングソフトウェアにおいて基本的なツールですが、無制限のメモリー割り当て問題があります。現在、再帰は通常どおり動作しますが、将来のZigバージョンではスタックオーバーフローからの保護が提供される予定です。
ライフタイムと所有権: ポインターが指すメモリが使用できなくなった場合に、そのポインターにアクセスしないようにすることが、Zigプログラマーの責任です。所有権とライフタイムのセマンティクスは、ポインターの所有者とメモリーの使用可能な期間を明確にするために、関数やデータ構造のAPIドキュメントで説明されるべきです。
Zigは、メモリー管理に関する柔軟性と制御を提供し、プログラマーがそのアプリケーションのニーズに合わせて最適なアロケーターを選択できるようにしています。
Zig言語はプログラマーに代わってメモリー管理を行うことはありません。このため、Zigにはランタイムがなく、Zigのコードはリアルタイム・ソフトウェア、OSカーネル、組込み機器、低遅延サーバーなど、多くの環境でシームレスに動作します。その結果、Zigのプログラマーは常に次のような問いに答えられなければなりません[https://ziglang.org/documentation/master/#Memory Memory]。
Zigと同様、C言語もメモリー管理を手動で行っています。しかし、Zigとは異なり、C言語にはmalloc、realloc、freeというデフォルトのアロケーターがあります。libc とリンクするとき、Zig はこのアロケーターを std.heap.c_allocator で公開します。しかし、慣習として、Zig にはデフォルトのアロケーターはありません。代わりに、割当てが必要な関数は Allocator パラメーターを受け取ります。同様に、std.ArrayList のようなデータ構造もその初期化関数で Allocator パラメーターを受け付けます。
allocator.zig:
const std = @import("std");
const Allocator = std.mem.Allocator;
const stdout = std.io.getStdOut().writer();
pub fn main() !void {
    var buffer: [100]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();
    const result = try concat(allocator, "foo", "bar");
    try stdout.print("concat(allocator, \"foo\", \"bar\") ⇒ \"{s}\"\n", .{result});
}
fn concat(allocator: Allocator, a: []const u8, b: []const u8) ![]u8 {
    const result = try allocator.alloc(u8, a.len + b.len);
    std.mem.copy(u8, result, a);
    std.mem.copy(u8, result[a.len..], b);
    return result;
}
実行結果:
concat(allocator, "foo", "bar") ⇒ "foobar"
上記の例では、スタック上の 100 バイトのメモリーが FixedBufferAllocator の初期化に使われ、それが関数に渡されます。
利便性のために std.testing.allocator でグローバルな FixedBufferAllocator が用意されており、基本的なリーク検出も行うことができます。
Zig には std.heap.GeneralPurposeAllocator でインポート可能な汎用アロケーターがあります。しかし、やはり、アロケーターの選び方の指針に従うことが推奨されます。
どのアロケーターを使うかは、様々な要因によって決まります。以下は、判断のためのフローチャートです[https://ziglang.org/documentation/master/#Choosing-an-Allocator Choosing an Allocator]。
ライブラリーを作っているのですか？この場合、パラメーターとしてアロケーターを受け取り、ライブラリーのユーザーにどのアロケーターを使うかを委ねるのがベストです。
libc をリンクしていますか? この場合、少なくともメインのアロケーターは std.heap.c_allocator が正しい選択だと思われます。
必要なバイト数の最大値は、コンパイル時に分かっている数で制限されていますか? この場合、スレッドセーフが必要かどうかによって std.heap.FixedBufferAllocator か std.heap.ThreadSafeFixedBufferAllocator を使ってください。
あなたのプログラムはコマンドラインアプリケーションで、基本的な循環パターンを持たずに最初から最後まで実行され（ビデオゲームのメインループやウェブサーバーのリクエストハンドラーなど）、最後にすべてを一度に解放することに意味があるようなものでしょうか？このような場合、このパターンに従うことをお勧めします。
const std = @import("std");
pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();
    const ptr = try allocator.create(i32);
    std.debug.print("ptr={*}\n", .{ptr});
}
ptr=i32@7fcdf92dd018
この種のアロケーターを使用する場合、手動で何かを解放する必要はありません。arena.deinit()を呼び出すと、すべてが一度に解放されます。
ビデオゲームのメインループやウェブサーバのリクエストハンドラのような周期的なパターンの一部でしょうか？例えば、ビデオゲームのフレームが完全にレンダリングされた後や、ウェブサーバーのリクエストが処理された後など、サイクルの終わりにすべてのアロケーションを一度に解放できる場合、std.heap.ArenaAllocator は素晴らしい候補となります。前の箇条書きで示したように、これによってアリーナ全体を一度に解放することができます。また、メモリの上限を設定できる場合は、std.heap.FixedBufferAllocator を使用すると、さらに最適化できることに注意しましょう。
テストを書いていて、error.OutOfMemoryが正しく処理されることを確認したいですか？この場合は std.testing.FailingAllocator を使ってください。
テストを書いていますか？この場合は std.testing.allocator を使ってください。
最後に、上記のどれにも当てはまらない場合は、汎用のアロケーターが必要です。Zigの汎用アロケーターは、設定オプションのcomptime構造体を受け取り、型を返す関数として提供されている。一般的には、メイン関数に std.heap.GeneralPurposeAllocator をひとつセットアップし、アプリケーションの様々な部分にそのアロケーターやサブアロケーターを渡していくことになる。
アロケーターの実装を検討することもできます。
Where are the bytes?
”foo” のような文字列リテラルは、グローバル定数データセクション( ''global constant data section'' )にあります。このため、文字列リテラルをミュータブルスライスに渡すとエラーになります[https://ziglang.org/documentation/master/#Where-are-the-bytes Where are the bytes]。
文字列リテラルと同様に、コンパイル時に値が分かっているconst宣言は、グローバル定数データセクションに格納されます。また、コンパイル時変数もグローバル定数データセクションに格納されます。
関数内のvar宣言は、その関数のスタックフレームに格納されます。関数から戻ると、関数のスタックフレームにある変数へのポインターは無効な参照となり、その参照解除は未確認の未定義動作となります。
Zig プログラマーは Allocator インターフェースを満たすことで、自分自身のアロケーターを実装することができます。そのためには、std/mem.zig のドキュメントコメントをよく読んで、allocFn と resizeFn を指定する必要があります[https://ziglang.org/documentation/master/#Implementing-an-Allocator Implementing an Allocator]。
インスピレーションを得るために、多くのアロケータの例を見ることができます。std/heap.zig と std.heap.GeneralPurposeAllocator を見てください。
多くのプログラミング言語では、ヒープ割り当てに失敗した場合、無条件にクラッシュすることで対処することにしています[https://ziglang.org/documentation/master/#Heap-Allocation-Failure Heap Allocation Failure]。
Zig のプログラマは、慣習として、これが満足のいく解決策であるとは考えていません。
その代わり、error.OutOfMemoryはヒープ割り当ての失敗を表し、Zigライブラリーはヒープ割り当ての失敗で処理が正常に完了しなかったときはいつでもこのエラーコードを返します。
Linuxなどの一部のOSでは、デフォルトでメモリーのオーバーコミットが有効になっているため、ヒープ割り当ての失敗を処理することは無意味であると主張する人もいます。この理由には多くの問題があります。
オーバーコミット機能を持つのは一部のOSだけである。
Linuxはデフォルトで有効になっていますが、設定可能です。
Windowsはオーバーコミットしません。
組込みシステムはオーバーコミットしません。
ホビー用OSはオーバーコミットがあってもなくてもよい。
リアルタイムシステムでは、オーバーコミットがないだけでなく、通常、アプリケーションごとの最大メモリ容量があらかじめ決められています。
ライブラリーを作成する場合、コードの再利用が主な目的の1つです。ライブラリーの作成において、コードの再利用は重要な目的の一つである。
オーバーコミットが有効であることに依存しているソフトウェアもありますが、その存在は数え切れないほどのユーザー体験の破壊の原因となっています。オーバーコミットを有効にしたシステム、例えばLinuxのデフォルト設定では、メモリーが枯渇しそうになると、システムがロックして使えなくなる。このとき、OOM Killer はヒューリスティックに基づき kill するアプリケーションを選択します。この非決定的な判断により、重要なプロセスが強制終了されることが多く、システムを正常に戻すことができないことがよくあります。
再帰( ''Recursion'' )はソフトウェアをモデリングする際の基本的なツールである。しかし、再帰にはしばしば見落とされがちな問題があります[https://ziglang.org/documentation/master/#Recursion Recursion]。
再帰はZigで活発に実験されている分野であり、ここにある文書は最終的なものではありません。[https://ziglang.org/download/0.3.0/release-notes.html#recursion 0.3.0のリリースノートで、再帰の状況を要約]して読むことができます。
簡単にまとめると、現在のところ再帰は期待通りに動作しています。Zigのコードはまだスタックオーバーフローから保護されていませんが、Zigの将来のバージョンでは、Zigのコードからのある程度の協力が必要ですが、そのような保護を提供することが予定されています。
ポインターを指しているメモリーが利用できなくなったときに、ポインターにアクセスしないようにするのは、Zigのプログラマーの責任です。スライスは、他のメモリーを参照するという点で、ポインターの一種であることに注意してください
[https://ziglang.org/documentation/master/#Lifetime-and-Ownership Lifetime and Ownership]。
バグを防ぐために、ポインターを扱うときに従うと便利な規則があります。一般に、関数がポインターを返す場合、その関数のドキュメントでは、誰がそのポインターを「所有」しているかを説明する必要があります。この概念は、プログラマーがポインターを解放することが適切である場合、そのタイミングを判断するのに役立ちます。
例えば、関数のドキュメントに「返されたメモリーは呼び出し元が所有する」と書かれていた場合、その関数を呼び出すコードは、いつそのメモリーを解放するかという計画を持っていなければなりません。このような場合、おそらく関数は Allocator パラメーターを受け取ります。
時には、ポインターの寿命はもっと複雑な場合があります。例えば、std.ArrayList(T).items スライスは、新しい要素を追加するなどしてリストのサイズが次に変更されるまで有効です。
関数やデータ構造のAPIドキュメントでは、ポインターの所有権と有効期限について細心の注意を払って説明する必要があります。所有権とは、ポインターが参照するメモリーを解放する責任が誰にあるかということであり、寿命とは、メモリーがアクセス不能になる時点（未定義動作が発生しないように）を決めることです。
ZigはC言語から独立しており、他の多くの言語とは異なりlibcに依存しませんが、Zigは既存のC言語で書かれたとの相互作用の重要性を認めています[https://ziglang.org/documentation/master/#C C]。
ZigはC言語との相互運用を容易にするために、いくつかの方法を用意しています。
以下に示すZigの型はC言語とのABI互換性が保証されており、他の型と同様に使用することができます[https://ziglang.org/documentation/master/#C-Type-Primitives C Type Primitives]。
C の void 型と相互運用する場合は anyopaque を使用します。
組込み関数 @cImport を使用すると、.h ファイルからシンボルを直接インポートすることができます[https://ziglang.org/documentation/master/#Import-from-C-Header-File Import from C Header File]。
@cImport 関数は、パラメーターとして式を受取ります。この式はコンパイル時に評価され、プリプロセッサー指令の制御や複数の.hファイルをインクルードするために使用されます。
align
ポインターのアライメントを指定するために使用します。
また、変数や関数の宣言の後に使用して、その変数や関数へのポインターのアライメントを指定することができます。
allowzero:ポインターの属性 allowzero は、ポインターのアドレスが 0 であることを許可します。
and:論理積
anyframe:関数フレームへのポインターを保持する変数の型として使用することができます。
anytype:関数のパラメーターは、型の代わりにanytypeで宣言することができます。型は関数が呼出された場所で推測されます
asm:インラインアセンブリ式を開始します。これにより、コンパイル時に生成される機械語を直接制御することができます。
async:関数呼出しの前に使用することで、関数がサスペンドされたときにそのフレームへのポインターを取得することができます。
await:await の後に提供されるフレームが完了するまで、現在の関数を中断するために使用することができます。await は、ターゲット関数のフレームから返される値を呼出し側にコピーします。
break:ブロックラベルと一緒に使うことで、ブロックから値を返すことができます。また、ループが自然に終了する前に終了させることもできます。
callconv:関数の呼出し規則を変更します。
catch:前の式がエラーと評価された場合に、その式を評価するために使用することができます。catchの後の式は、オプションでエラーコードをキャプチャーすることができます。[TODO:クリーンアップ]
comptime:コンパイル時に変数や関数パラメーターが既知であることを示すために、宣言の前に comptime を使用することができます。また、コンパイル時に式の実行を保証するために使用することもできます。
const:変更できない変数を宣言します。また、ポインターの属性として使用すると、ポインターが参照する値を変更できないことを示す。
continue:ループの中で使うと、ループの最初にジャンプして戻ることができます。
defer:制御フローが現在のブロックから離れるときに実行する式やブロックを登録します。
else
if、switch、while、forの代替ブランチ( ''alternate branch'' )を提供するために使用できます。
if の後で使用した場合、elseブランチ はテスト値が false、null、またはerrorを返した場合に実行されます。
switch式の中で使用した場合、elseブランチはテスト値が他のケースと一致しない場合に実行されます。
ループの後で使用した場合、else分岐はループがブレークせずに終了した場合に実行されます。
if, switch, while, forも参照してください。
enum:enum型を定義します。
errdefer:関数がエラーを返した場合、制御フローが現在のブロックを離れるとき実行する式やブロックを登録します。
error:error型を定義します。
export:生成されたオブジェクトファイルにおいて、関数や変数を外部から見えるようにします。エクスポートされた関数は、デフォルトでC言語の呼出し規則( ''calling convention'' )に従います。
extern:静的にリンクする場合はリンク時に、動的にリンクする場合は実行時に解決される関数や変数を宣言するために使用することができます。
fn:関数を宣言します。
for:スライス、配列、タプルの要素に対して反復処理を行うために使用します。
if:論理式、オプショナル値、エラーユニオンをテストすることができます。オプショナル値やエラーユニオンの場合、if はラップされていない値をキャプチャーすることができます。
inline:コンパイル時にループ式が展開されるようにラベル付けするために使用されます。また、関数のすべての呼出し箇所で強制的にインライン化することもできます。
noalias:TODO add documentation for noalias
nosuspend
ブロック、文、式の前で使用することができ、サスペンドポイントに到達しないスコープをマークすることができます。
suspend キーワードを使用すると、コンパイルエラーになります。
まだ完了していない関数フレームで await を使用すると、安全が確認された未定義の動作になる。
非同期関数を呼出すと、await を含む await async some_async_fn() と等価であるため、安全が確認された未定義の動作になることがある。
nosuspendスコープ内のコードは、それを囲む関数を非同期関数にすることはありません。
opaque:サイズとアライメントが未知の（ただしゼロではない）新しい型を宣言します。
or:論理和
orelse:前の式が null と評価される場合に、その式を評価するために使用することができます。
packed:struct 定義の前にキーワード packed を指定すると、そのstructのメモリー内レイアウトが保証された packed レイアウトに変更されます。
pub:トップレベル宣言の前にある pub は、その宣言が宣言されているファイルとは異なるファイルから参照できるようにます。
resume:関数が中断された時点から、その関数のフレームを継続して実行します。
return:値を伴って関数を終了させます。
linksection:TODO add documentation for linksection
struct:struct を宣言します。
suspend:制御フローを関数の呼出し側または再呼出し側に戻します。suspendは、関数内のブロックの前にも使用でき、制御フローが呼出し側に戻る前に関数がそのフレームにアクセスできるようにします。
switch:共通の型の値をテストするために使用することができます。switchケースは、タグ付けされたユニオンのフィールド値をキャプチャーすることができます。
test:動作が期待に沿うことを確認するために使用されるコードのトップレベルブロックを示すために使用します。
threadlocal:変数をスレッドローカルとして指定するために使用します。
try:エラーユニオン式を評価する。もしそれがエラーであれば、同じエラーで現在の関数から戻る。そうでない場合は、式はラップされていない値になります。
union:unionを定義します。
unreachable:制御フローが特定の場所に到達することがないことを保証するために使用することができます。ビルドモードによっては、unreachable はパニックを発生させるかもしれません。
usingnamespace:struct、union、または enum でなければならないオペランドのすべての公開宣言を現在のスコープにインポートするトップレベルの宣言を行います。
var:変更可能な変数を宣言します。
volatile:ポインターのロードやストアに副作用があることを示すために使用されます。また、インラインアセンブリ式を修正して、副作用があることを示すこともできます。
while:boolean、optional、errorの各ユニオン式を繰返しテストし、それぞれfalse、null、errorと評価された時点でループを停止するために使用されます。
Zigは、2024年2月10日時点で pre-release の段階にあり、インストール手順も何度か変わっているので、まずは https://ziglang.org/learn/getting-started/ を参照し、最新のインストール手順を確認してください。
ローカルにコンパイル実行環境を作るのが困難、あるいは手間を掛けずにコンパイルと実行を試してみたい。そんな場合には、オンラインコンパイル実行環境を使うことも検討に値します。
[https://zig-play.dev/ Zig Playground]
公式のオンラインコンパイル実行環境です。
フォーマッターも利用できます。
[https://wandbox.org/ Wandbox]
複数のプログラミン言語に対応しており、Zigにも対応しています。
[https://codapi.org/zig/ codapi]
複数のプログラミン言語に対応しており、Zigにも対応しています。
test 機能にも対応しています。
nightly の Zigを使いたい場合でない限り、お使いのOSやディストリビューションでサポートされているパッケージ マネージャーを使ってインストールする事をお勧めします。
また、ローカルのパッケージデータベースをパッケージシステムのリポジトリーと同期を取り、鮮度の高い状態を維持するよう心がけてください。これは、インストール後も同じですし、zigに限ったことでもありません。
Windows
Zig は、Chocolateyパッケージシステムが対応しています。
> choco install zig
macOS
macOS では、HomebrewとMacPortsが対応しています。
FreeBSD 
GNU/Linuxのディストリビューション
いわゆるLinuxは、Linux（ここではOSカーネル）と、FSFがHurdカーネルのために設計・開発したGNUユーザーランド（OSの基本機能を提供するソフトウェアの集合）を組合わせたものです。
このように、LinuxカーネルとGNUユーザーランドを組合わせたソフトウェアプラットフォームをGNU/Linuxと呼びますGNU/Linuxの中核として、商用・非商用を問わず、再配布可能なユーティリティを収集・配布するディストリビューターが登場しました。これらのディストリビューターは、それぞれの配布物（互換性を失いがち）を、ディストリビューションとして区別する必要が生じました（特に、共有ライブラリーの非互換性は目立ち、Linux支持者自身が嫌悪するWindowsのDLL-Hellに酷似しています)。 このようにして、ディストリビューション間の区別がなされたのです(また、ほかにもマーケティング上の理由などもあります)。Unixでディストリビューションという言葉は、ソースコードで配布されるBSDのD (''distribution'') と関連付けられ、一方、非ソースコード指向のGNU/Linuxディストリビューションが、Unix訴訟の間隙を利する形で386 BSDのニッチをに受入れているのは皮肉なことです。
$ sudo snap install zig --beta --classic
Zig は、ソースコードが Github の https://github.com/ziglang/zig.git で公開されているので、必要に応じてソースコードからビルドすることができます。
Zigは、セルフホスティングZigコンパイラーを始めとする、Zigの言語処理系とツールチェインやユーティリティーなどは、Zig自身で書かれています。なので、最初にバイナリーを入手してブートストラップするか、クロスビルドしたバイナリーを持込むか、パッケージシステムからインストールし、ターゲットでセルフコンパイル出来る状態を作る方法があります（FreeBSDのPortsが行っているのは、まさにこれです）。
ビルド方法こそ頻繁に内容が変わるので、個別具体的な手順は述べませんが、zig はコンパイラーであるとともにツールチェインでもあり、ビルドシステムも内包しているので、
zig buildとすると build.zig ファイルに書かれているレシピにしたがって自動的にビルドが進行します（ストレージとメモリーと時間に余裕を見る必要があります）。
Zig の処理系 zig という１つのコマンドで、コンパイラーだけでなく、アーカイバー・C/C++コンパイラーやテストフレームワーク・フォーマッター・ビルドツールなどのツールチェインが統合されています。
zig コマンドを tcsh から実行:
% uname -a
FreeBSD localhost 14.0-STABLE FreeBSD 14.0-STABLE #0 60d9bb14e: Tue Dec 26 01:07:17 JST 2023     user1@localhost:/usr/obj/usr/src/amd64.amd64/sys/OLYMPIC amd64
% zig version
0.11.0
% cat hello.zig
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {s}!\n", .{"world"});
}
% zig run hello.zig
Hello, world!
Zig言語のzig runコマンドは、Zigコンパイラを使用してZigソースコードをコンパイルし、実行可能なバイナリを生成して実行するためのコマンドです。このコマンドを使うと、Zigソースコードを直接実行することができます。
公式ドキュメント
Zigの最も包括的なチュートリアルは、公式ドキュメントにあります。ドキュメントには、Zigの基本的な概念、プログラミングスタイル、および標準ライブラリの使用方法が説明されています。ここでは、基本から上級まで、詳細に解説されています。
公式ドキュメントは以下からアクセス可能です。
ZigLearn
「ZigLearn」とは、Zigというプログラミング言語を学ぶためのオンラインリソースの集まりです。Zigは、C言語に似た構文を持ち、メモリセーフでありながら高速なプログラムを書くことができる言語です。このサイトでは、Zigを初めて学ぶ人から、より高度なトピックに取り組みたい人まで、様々なレベルの学習者に向けたリソースが提供されています。
ZigLearnは以下からアクセス可能です。
Zig Learn X in Y Minutes
「Zig Learn X in Y Minutes」というウェブサイトは、簡潔で理解しやすい形式でZigの紹介をしています。このチュートリアルでは、Zigでプログラムを作成する基本的なステップを紹介します。
Zig Learn X in Y Minutesは以下からアクセス可能です。