本書は、Zigのチュートリアルです。 Zigは、堅牢で最適かつ再利用可能なソフトウェアを維持するための汎用システムプログラミング言語およびツールチェインです[1]。 Zigは、アンドリュー・ケリー( Andrew Kelley )によって設計され、静的で強い型付けで型推論とジェネリックプログラミングをサポートします。

Wikipedia
Wikipedia
ウィキペディアZig (プログラミング言語)の記事があります。

概要 編集

Zigは、2016年2月に発表された比較的若いプログラミング言語で[2]、2022年11月現在の最新バージョンは 0.10.0 で、pre-release と位置づけられています[3]。このため Hello world ですら、バージョン間で互換性がなくなることもあり、今後もリリースバージョンまでは言語仕様やライブラリーおよびツールチェインの仕様が変更される可能性があります。

Hello world の変遷 編集

Zig Language Referenceの、Hello worldの変遷(新しい順)。

master@2023-07-31[4]
0.8.1に同じ
0.10.1[5]
0.8.1に同じ
0.10.0[6]
0.8.1に同じ
0.9.1[7]
0.8.1に同じ
0.8.1[8]
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {s}!\n", .{"world"});
}
{}{s}[9]
0.7.1[10]
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {}!\n", .{"world"});
}
s/outStream/writer/
0.6.0[11]
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().outStream();
    try stdout.print("Hello, {}!\n", .{"world"});
}
初期化の初期値から try がなくなった。
0.4.0[12]
0.2.0に同じ
0.5.0[13]
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_filevar から const に変更された。
0.3.0[14]
0.2.0に同じ
0.2.0[15]
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[16]
const io = @import("std").io;
pub fn main() -> %void {
    %%io.stdout.printf("Hello, world!\n");
}

特徴 編集

Zigは、コンパイル時に安全で高速なシステムプログラミング言語です。以下に、Zigの主な特徴を詳しく説明します。

  1. 静的型付け: Zigは静的型付けを採用しています。型はコンパイル時に解決され、実行時に型エラーを引き起こすことがありません。また、型推論もサポートされています。
  2. 値型: Zigは、値型を採用しています。これは、オブジェクトのコピーが作成されるため、値型はポインタ型よりも安全で、予測可能な動作をします。
  3. コンパイル時メタプログラミング: Zigには、コンパイル時メタプログラミングをサポートするためのcomptimeブロックがあります。これにより、コンパイル時に計算を実行し、コンパイル時に情報を収集することができます。
  4. メモリ管理: Zigは、メモリ管理について堅牢で安全なアプローチを採用しています。メモリリークやダングリングポインタなどの問題を回避するために、コンパイル時にメモリ安全性を検査することができます。
  5. モジュールシステム: Zigには、モジュールシステムがあります。モジュールは、依存関係を管理するための仕組みを提供します。
  6. ネイティブコード生成: Zigは、ネイティブコードを生成するコンパイラを提供します。これにより、高速かつ効率的なコードを実行することができます。
  7. エラーハンドリング: Zigには、エラーハンドリング機能があります。エラーハンドリングは、C言語のエラーコードや例外処理のようなアプローチに比べて、より安全で予測可能な動作をすることができます。

以上が、Zigの主な特徴のいくつかです。これらの特徴により、Zigは高速かつ安全なシステムプログラミングを可能にします。

以下、個別の要素に言及します。

コンパイル型言語
1つまたは複数のソースコードをコンパイルして実行ファイルを得、それを実行します。
静的型付け
値・変数・関数のパラメーター・関数の戻値などの型はコンパイル時に検証され、型安全性が担保されます。
例外はありません
エラー処理などのための例外はありません。この用途には、関数の戻値にエラーユニオン型オプショナル型を使います。
演算子オーバーロードはありません
演算子は演算子一覧表にある通りにしか機能しませんし、必ずそのように機能します。シフト演算子がストリーム入出力を行ったりはしません。
関数オーバーロードはありません
関数に与えられたパラメーターによって違う関数が呼び出されることはありません。ただし、可変引数の関数は定義できます。その場合も、同じ関数が呼び出されます。
型推論が行われます
変数宣言時に与えられた初期値(必須)から変数の型を推論することで型アノテーションを省略できます。
ガベージコレクション
ガベージコレクションはありません。
クラスはありませんがメソッドはあります
クラスはありませんが、コンテナー( struct, union, enum )がメソッドを持つことができます。
コンストラクターはありません
慣習的に init() と名付けられたメソッドがコンテナーのインスタンス化に使われます。
デストラクターはありません
慣習的に deinit() と名付けられたメソッドがコンテナーのインスタンスの解体処理に使われます。deinit() はスコープでを抜ける時に自動的に実行されませんdefer myObj.deinit(); のようにインスタンスの生成時に解体処理を登録します。
継承はありません
タグ付きunionusingnamespaceを使うと継承でしたいこと(=差分プログラミング)が可能ですが、構文や仕組みは異なります。
interface や protocol はありません
JavaGo の interface や Swift の protocol はありませんが、コンパイル時にメソッドが定義されているかテストし、なければエラーにするプログラミングはできます。
名前空間の役割はコンテナーが担います
namespace の様なキーワードはありませんが、ドットシンタックス(コンテナー.識別子 や コンテナー変数.識別子)でコンテナーが名前空間の役目を果たします。また、usingnamespace を使うとドットシンタックスなしに公開識別子にアクセスできるようになります(ミックスインとも言えます)。
ジェネリックプログラミングに対応しています
関数呼出しの引数に(コンパイル時に既知の)型を渡すことができるので、ジェネリックプログラミングを行うことが出来ます。
ソースファイルは匿名struct
コンパイル単位であるソースファイルは暗黙のうちに匿名structで、組込み関数[[#@import|@import]() が返す値を識別子に束縛することで名前空間を構成します。ex: const std = @import("std")
インスタンスがスコープを抜けるときに実行する処理を登録できます
スコープを抜ける理由によって、 defer と errdefer の2種類の処理を登録できます。
多くの制御構造は式と文の構文を持ちます
if, while, for の3つの制御構文は、値を返す式構文とブロックを持つ文構文の両方を持ちます。switchは、値を返す式構文しかありません。
文末の;(セミコロン)の省略はできません
最近の新興言語にして珍しく ; は省略できません。
ループ構文は else 節を持つことができます
whilefor の2つの反復構文は、ループを「完走」した時に実行する else 節を持つことができます。
キーワードを識別子にできます
PL/Iのように無制限にではなく @"else" のような形式でキーワードや数字で始まる識別子を使うことができます。
シャドウイング禁止
外部スコープで使われている識別子と同じ識別子を内側のスコープで使うことは出来ません。コンパイルエラーになります。
関数のパラメーターはイミュータブル
関数のパラメーターは、 const 宣言された変数(=定数)と同じく書換えることは出来ません。

サンプルコード・ギャラリー 編集

実際にZigで書かれたのコードを見てみましょう。

都市間の大圏距離 編集

Ruby#ユーザー定義クラスの都市間の大圏距離を求めるメソッドを追加した例を、Zigに移植しました。

都市間の大圏距離
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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;
        }
        return 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 print("{s}: {}\n", .{ site.name, site.gc });
    }

    try print("---\n", .{});

    inline for (sites) |a, index| {
        const b = sites[(index + 1) % sites.len];
        try 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]
formatメソッドは特別な名前で、std.fmt を拡張しコレクションの固有の文字列化メソッドを提供します。
Zigでは、文字列を含む配列は第一級オブジェクトではないので、ストリーム入出力を拡張する方法が安全性を考えても手堅いです。
for の前の inline は、コンパイル時にループを展開する事を意味して、タプルの配列であることをコンパイラーに教えています。

ファイルの読出し 編集

ファイルを開いて内容をアロケートしたバッファに読出し標準出力に出力する例。

ファイルの読出し
const std = @import("std");
const stdout = std.io.getStdOut().writer();

pub fn main() !void {
    const file = try std.fs.cwd().openFile(
        "/etc/hosts",
        .{ .read = true },
    );
    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);
}

ジェネリックな複素数型 編集

ジェネリックプログラミングを使った複素数型を実装してみました。

ジェネリックな複素数型
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub 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(lhs: Self, rhs: Self) Self {
            return Self{ .real = lhs.real + rhs.real, .imag = lhs.imag + rhs.imag };
        }
        pub fn addTo(self: *Self, other: Self) Self {
            self.*.real += other.real;
            self.*.imag += other.imag;
            return self.*;
        }
    };
}

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 print("{}\n", .{f64cplx1});
    try print("{}\n", .{f64cplx2});
    try print("{}\n", .{F64Complex.add(f64cplx1, f64cplx2)});
    var f64cplx3 = F64Complex.init(-1.0, -14.0);
    try print("{}\n", .{f64cplx3.addTo(f64cplx2)});
}
実行結果
Complex(f64){ .real = 3.0e+00, .imag = 4.0e+00 }
Complex(f64){ .real = 1.0e+00, .imag = -4.0e+00 }
Complex(f64){ .real = 4.0e+00, .imag = 0.0e+00 } 
Complex(f64){ .real = 0.0e+00, .imag = -1.8e+01 }
pub fn Complex(comptime T: type) typeは、要素型をパラメーターに複素数型を返します(複素数ではなく複素数型です)。
Zig には演算子オーバーライドがないのでメソッドとして定義します。
2つの複素数を足し、結果を別の複素数として返す add()
複素数に別の複素数を破壊的に足す addTo()
この調子で sub()/subFrom(), mult()/multBy(), div(),divBy() も定義します()
本当は adddTo() で左辺値式を返しメソッドチェインを可能にしたかったのですが方法が思いつきませんでした(無理?)。

[TODO:意図が判りやすいようにリファクタリング]

ソート 編集

標準ライブラリーのソート機能の使用例。

ソートの例
const std = @import("std");
const stdout = std.io.getStdOut().writer();

pub fn main() !void {
    const int = i32;
    var ary = [_]int{ 1, 4, 1, 4, 2, 1, 3, 5, 6 };

    try stdout.print("befor: {any}\n", .{ary});

    std.sort.sort(int, ary[0..], {}, struct {
        pub fn cmp(context: void, a: int, b: int) bool {
            return std.sort.asc(int)(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.sort()は、型・配列・context・比較関数をパラメーターに配列をソートします。
Zigには、ラムダ式がないので匿名structのメソッドを比較関数に使いました。
この場合はstd.sort.sort(int, ary[0..], {}, comptime std.sort.asc(int));で十分ですが
structの配列のあるフィールドをキーにそうとしたい場合は、匿名structのメソッドを比較関数に使う手口が有効ですし、ソート対象のstructのコードに手を入れられるときは、structのメソッドにしても良いでしょう(ラムダ式ほしい)。

エラトステネスの篩 編集

エラトステネスの篩を、若干 Zig らしく書いてみました。

エラトステネスの篩
const std = @import("std");
const print = std.io.getStdOut().writer().print;

fn Eratosthenes(comptime n: u16) !void {
    var sieve: [n + 1]bool = undefined;
    for (sieve) |*e, i| {
        e.* = i >= 2;
    }
    for (sieve) |_, i| {
        if (!sieve[i]) {
            continue;
        }

        try print("{} ", .{i});
        var j = 2 * i;
        while (j <= n) : (j += i) {
            sieve[j] = false;
        }
    }
}

pub fn main() !void {
    try Eratosthenes(100);
}
実行結果
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

抽象クラス(の代替) 編集

Java/抽象クラスを、Crystalに移植しました例を題材に、Zigにはない抽象クラスの代替のコード例を書いてみました。

抽象クラス(の代替)
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 @intToFloat(f32, 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 print("{}({})\n", .{ @TypeOf(x), x });
    }

    try print("\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 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には範囲型がないので代替のコード例を書いてみました。

範囲型(の代替)
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 print("{} ", .{i});
    }
}
実行結果
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

チートシート 編集

エントリーポイント
エラーユニオン型が返る可能性を考慮しない場合
pub fn main() void {}
エラーユニオン型が返る可能性を考慮する場合
pub fn main() !void {}
コメント
// から行末までがコメントです。
// /* ... */ スタイルのコメントはありません。
変数宣言
イミュータブル
const 識別子 : = 初期化式 ;
型推論版
const 識別子 = 初期化式 ;
ミュータブル
var 識別子 : = 初期化式 ;
リテラル
整数リテラル
123, 0b11010, 0o177, 0xbadbeef
浮動小数点数リテラル
3.14, 1.2e-9
文字リテラル
'a', '漢', '💯'
文字列リテラル
abc, "了解🏝👅"
複数行に渡る文字列リテラル
\\ 複数行に渡る場合は
\\ この様に
\\ \\ を前置することで
\\ 記述できます。
配列リテラル
[4]u16{ 2,3,5,7 }, [_]u16{1,2,3,4,5}
制御構造
分岐
if
else節のないif式
if ( 条件式 )
条件式が false でなければif式の値は 式
false ならば void
if式
if ( 条件式 ) 式1 else 式2
条件式が false でなければif式の値は 式1
false ならなば 式2
if文
if ( 条件式 ) {
    // ブロック
}
条件式が false でなければブロックを実行
else節あり
if ( 条件式 ) {
    // ブロック1 
} else {
    // ブロック2
}
条件式が false でなければブロック1を実行
else ならばブロック2を実行
条件式がオプショナル型
if ( オプショナル式 ) | 変数 | {
    // ブロック1
} else {
    // ブロック2
}
オプショナル式が
null でなければ変数にキャプチャーしブロック1を実行
null ならばブロック2を実行
条件式がエラーユニオン型
if ( エラーユニオン式 ) | 変数 | {
    // ブロック1
} else | err | {
    // ブロック2
}
エラーユニオン式が
エラーコードでなければ変数にキャプチャーしブロック1を実行
エラーコードならば err にキャプチャーしブロック2を実行
switch
switch (  ) {
    値1 => 式1,
    値2a, 値2b => | 変数2 | 式2, // 変数2にキャプチャー
    値3a => | *変数3 | 式3, // 変数3にポインターをキャプチャー
  elase => 式x
}
反復
while
値を返す
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 でなければ変数にキャプチャー
  // ブロック1
} else { // null だったとき
  // ブロック2
}
エラーユニオン型
while (エラーユニオン式) | 変数 | { // エラーコードでなければ変数にキャプチャー
  // ブロック1
} else |err| { // エラーコードを err にキャプチャー
  // ブロック2
}
for
値を返す
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
structリテラル
struct型{
    .メンバー1 = 式1,
    .メンバー2 = 式2,
    .メンバーn = 式n
}
匿名structリテラル(タプル)
.{
    式1,
    式2,
    式n
}
enum
enumリテラル
enum型.メンバー
union
unionリテラル
uniont型{ .メンバー = }
タグ付きunion
関数
関数定義

基礎篇 編集

[TODO:書くべき項目を並べてみましたが、例えば「値と型」だけでも網羅的に書いていくとコンテンツの分量が爆発するのが目に見えているので、過剰になったらリファレンス篇に移動するなどの方法で、各節はコンパクトさを心がけたい]

エントリーポイント 編集

Zigでは、関数 mainがエントリーポイントです。

nop.zig
pub fn main() void {}

なにもしないプログラムはこの様になりますが、エラー集合型を返す可能性があるときは

pub fn main() !void {
   // エラー集合型を返す可能性がある処理
}

と戻値の型を void から !void に代えます。

コメント 編集

Zigでは、// から行末までがコメントです。 C言語の /* … */ のスタイルの複数行に渡るコメントはありません。 これは、コードの各行を文脈に関係なくトークン化できるようにするためです。

hello.zig
// hello.zig:
const std = @import("std"); // 先頭に @ が付く関数は組込み関数です
pub fn main() !void { // 次の行の try 演算子がエラー集合を返す可能性があるので !void が戻値型
   try std.io.getStdOut().writeAll("Hello, World!\n");
}
Builtin Functions

Docコメント 編集

Zigでは、/// から始まるコメントは特別なコメントで、Docコメント( Doc comments )と呼ばれます。 Docコメントは特定の場所にしか許されません。式の途中や非Docコメントの直前など、予想外の場所にdocコメントがあると、コンパイルエラーになります。

[TODO:サンプルコードと整形結果]

トップレベルDocコメント 編集

Zigでは、//! から始まるコメントは特別なコメントで、トップレベルDocコメント( Top-Level Doc Comments )と呼ばれます。 コンテナレベルのドキュメントのように、直後のドキュメントに属さないユーザードキュメントに、トップレベルDocコメントを使います。

[TODO:サンプルコードと整形結果]

DocコメントおよびトップレベルDocコメントは、コンパイル時に zig build-exe -femit-docs ソースファイル.zig の様に、-femit-docs をあたえると、 docs/ 以下にドキュメントが生成されます。

値と型 編集

[TOD0:整数浮動小数点数bool文字列unionstructenum配列ベクトルスライスポインター・ゼロビットな型, 関連する組込み関数]

formatを伴うprintと値と型
pub fn main() !void {
    const print = @import("std").io.getStdOut().writer().print;
    try print(" (1) = {}\n", .{42});
    try print(" (2) = {}\n", .{0x17});
    try print(" (3) = {}\n", .{0o17});
    try print(" (4) = {}\n", .{0b0100101});
    try print(" (5) = {}\n", .{1e222});
    try print(" (6) = {}\n", .{3.1415926536});
    try print(" (7) = {}\n", .{'c'});
    try print(" (8) = {c}\n", .{'c'});
    try print(" (9) = {s}\n", .{"abcdef"});
    try print("(10) = {}, {}\n", .{ 111, 999 });
    try print("(11) = {1}, {0}\n", .{ 111, 999 });
    try print("(12) = {1s}, {0}\n", .{ 111, "abc" });
    try 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()の前の、tryは単項演算子です。
try は、右の式のエラーユニオン式を評価します。もしエラーであれば、同じエラーで現在の関数から戻ます。そうでない場合は、式はラップされていない値になります。
エラーユニオン型( Error Union Type )を返す関数は、try単項演算子かcatch二項演算子で、値とエラーを弁別する必要があります(tryあるいはcatchがないと、コンパイル時にエラーになります)。
An error occurred:
/tmp/playground726918707/play.zig:3:10: error: error is ignored. consider using `try`, `catch`, or `if`    print(" (1) = {}\n", .{42});
                                                                                                                ^
print()の様に、標準ライブラリーの format()を使う関数は、書式化文字列タプル(匿名 struct ) .{ … } を引数にします。C言語のような、可変引数ではなくタプルを使うので[17]、プレースホルダーがない場合でも、空のタプル.{} は必須です。
書式化文字列
通常の文字列ですが {} で囲まれたプレスホルダーが、タプルの当該順位の値(を書式化した文字列)に置換わります。
書式化文字列の中で { あるいは } 自身を使いたいときには、{{ あるいは }} と二文字重ねます。
タプル
書式化文字列のプレースホルダーによって、参照と文字列化される値のタプルです。
2つ以上の値を渡す場合は、第二引数を .{ 1, 2, 3 } の様にカンマ区切りのタプルにします( {} の前の . (点)を忘れがちですが、型の省略を意味し必須です)。
基本的に、左から順にプレスホルダーにタプルの値が左から与えられますが、{0} {1} の書式で参照する引数の順位を明示できます。
書式指定と併用する時は、 print("? = {1s}, {0}\n", .{ 111, "abc" }) の様に順位が先、書式指定文字が後になります。
この機能は言語(自然言語)によって異なる語順を吸収することに使えそうですが、fmtの第一引数は comptime 修飾子がついていて変数にはできません。
数値(整数浮動小数点数)や文字リテラルと文字列リテラルがあり、整数はいくつかの異なる基数表現が、浮動小数点数は指数表現と小数表現があります。
文字と文字列は明確に異なり、リテラルでは ’A’ が文字([[#@TypeOf|@TypeOf]('A') ⇒ comptime_int)、 ”ABC” が文字列([[#@TypeOf|@TypeOf]("ABC") ⇒ *const [3:0]u8)です。
嫌な予感がした人の直感は正解です。Zig では、文字列は第一級オブジェクトではなく文字(u8)の配列で、関数から返すときはアロケーターと defer の連携などで記憶域の寿命と値の妥当性を「プログラマー」が担保する必要があります。また、GoのGCはありません。CrystalのASTを操作できるマクロもありませんし、Rustの所有権も、C++のスマートポインターもありません。
このことは、C言語なみのプログラマー任せのメモリー管理を文字列以外でも強いられることを意味していますが、zig(コマンド)や標準ライブラリーのソースコードを読むと、複数の型でアロケーターを使い分け、スタック上のインスタンス(のハードウェア起因のスコープ)を使い分けられることを実践で証明しているので、pre-releaseから、initial-releasまでの間に、安定化・定式化が図られることを期待します。

[TODO:master/lib/std/fmt.zigを観て書いています。参照すべき標準ライブラリーのドキュメントを出来たら/仕様が安定したら見直し]

プリミティブ型 編集

プリミティブ型( Primitive Types )[18]
相当するC言語の型 説明
i8 int8_t 符号付き8ビット整数
u8 uint8_t 符号無し8ビット整数
i16 int16_t 符号付き16ビット整数
u16 uint16_t 符号無し16ビット整数
i32 int32_t 符号付き32ビット整数
u32 uint32_t 符号無し32ビット整数
i64 int64_t 符号付き64ビット整数
u64 uint64_t 符号無し64ビット整数
i128 __int128 符号付き128ビット整数
u128 unsigned __int128 符号無し128ビット整数
isize intptr_t 符号付きポインターサイズ整数
usize uintptr_t, size_t 符号無しポインターサイズ整数
c_short short C言語とのABI互換性のため
c_ushort unsigned short C言語とのABI互換性のため
c_int int C言語とのABI互換性のため
c_uint unsigned int C言語とのABI互換性のため
c_long long C言語とのABI互換性のため
c_ulong unsigned long C言語とのABI互換性のため
c_longlong long long C言語とのABI互換性のため
c_ulonglong unsigned long long C言語とのABI互換性のため
c_longdouble long double C言語とのABI互換性のため
f16 _Float16 16ビット浮動小数点数(仮数10ビット) IEEE-754-2008 binary16
f32 float 32ビット浮動小数点数(仮数23ビット) IEEE-754-2008 binary32
f64 double 64ビット浮動小数点数(仮数52ビット) IEEE-754-2008 binary64
f80 double 80ビット浮動小数点数(仮数64ビット) IEEE-754-2008 80ビット拡張精度
f128 _Float128 128ビット浮動小数点数(仮数112ビット) IEEE-754-2008 binary64
bool bool true または false
anyopaque void 型消去されたポインター
void (該当なし) 0ビット型
noreturn (該当なし) break, continue, return, unreachable, and while (true) {} の型
type (該当なし) 型の型
anyerror (該当なし) エラーコード
comptime_int (該当なし) コンパイル時に既知の値に対してのみ許可される整数リテラルの型。
comptime_float (該当なし) コンパイル時に既知の値に対してのみ許可される浮動小数点リテラルの型。
上記の整数型に加え、任意のビット幅の整数を参照するには、識別子としてiまたはuに続けて数字を用いることができます。例えば、識別子 i7 は符号付き7ビット整数を意味します。この表現の整数型に許される最大ビット幅は65535です。

プリミティブ値 編集

プリミティブ型( Primitive Values )[19]
名前 説明
truefalse bool
null optional型を null に設定するために使用されます。
undefined 値を不定にするために使用されます。

文字列リテラルとUnicodeコードポイントリテラル 編集

構文(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]* )+
[20]
文字列リテラル 編集

文字列リテラルは、ヌル終端バイト配列への定数型単一項目ポインターです。文字列リテラルの型は、長さとヌル終端であるという事実の両方をコード化しているため、スライスとヌル終端ポインターの両方に強制することが可能です。文字列リテラルを再参照すると配列に変換されます[21]

Zigにおける文字列のエンコーディングは、事実上UTF-8であると仮定されています。ZigのソースコードはUTF-8でエンコードされているので、ソースコードの文字列リテラル内に現れる非ASCIIバイトは、そのUTF-8の意味をZigのプログラム内の文字列の内容に引き継ぎ、コンパイラーがそのバイトを修正することはありません。ただし、UTF-8以外のバイトを文字列リテラルに埋め込むことは可能で、その場合は \xNN 記法を使用します[21]

Unicodeコードポイントリテラル 編集

Unicodeコードポイントリテラルの型は comptime_int で整数リテラルと同じです。すべてのエスケープシーケンスは、文字列リテラルと Unicodeコードポイントリテラルの両方において有効です[21]

他の多くのプログラミング言語では、Unicodeコードポイントリテラルは「文字リテラル」と呼ばれます。しかし、Unicode仕様の最近のバージョン(Unicode 13.0時点)では、「文字」の正確な技術的定義は存在しません。Zigでは、Unicodeコードポイントリテラルは、Unicodeのコードポイントの定義に対応します。

エスケープシーケンス 編集
エスケープシーケンス( Escape Sequences )[22]
エスケープシーケンス 名称
\n Newline
\r Carriage Return
\t 水平タブ
\\ バックスラッシュ自身
\' シングルクォーテーション
\" ダブルクォーテーション
\xNN 16進8ビットバイト値(2桁)
\u{NNNNNN} 16進数 Unicode コードポイント UTF-8 符号化(1桁以上)
註:有効なUnicodeポイントの最大値は0x10ffffです。

複数行にまたがる文字列リテラル 編集

文字列リテラルを、エスケープ記号がなく複数行にまたがって書くことができます。複数行の文字列リテラルを開始するには、\\ トークンを使用します。コメントと同じように、文字列リテラルは行末まで続きます。行の終わりは文字列リテラルに含まれません。ただし、次の行が \\ で始まる場合は、改行が追加され文字列リテラルが続行されます。

複数行にまたがる文字列リテラル
const print = @import("std").io.getStdOut().writer().print;
const message =
    \\文字列リテラルを、エスケープ記号がなく複数行にまたがって書くことができます。
    \\複数行の文字列リテラルを開始するには、\\ トークンを使用します。
    \\コメントと同じように、文字列リテラルは行末まで続きます。
    \\行の終わりは文字列リテラルに含まれません。
    \\ただし、次の行が \\ で始まる場合は、改行が追加され文字列リテラルが続行されます。
;

pub fn main() !void {
    try print("message = {s}", .{message});
}
実行結果
message = 文字列リテラルを、エスケープ記号がなく複数行にまたがって書くことができます。
複数行の文字列リテラルを開始するには、\\ トークンを使用します。
コメントと同じように、文字列リテラルは行末まで続きます。
行の終わりは文字列リテラルに含まれません。
ただし、次の行が \\ で始まる場合は、改行が追加され文字列リテラルが続行されます

comptime 編集

Zigでは、コンパイル時に式が既知かどうかという概念を重要視しています。

このコードはコンパイルエラーになります。

定数の初期値が関数の戻値だとエラー
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:
/tmp/playground130713503/play.zig:7:17: error: unable to evaluate constant expression
    const ary: [len]i32 = undefined;
「定数式が評価できない」と宣っています。
C++であれば、constexprが適用なケースですが、Zigでは次のような解決方法を取ります。下記コードはエラーになりません。
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() の呼出しを comptime で修飾しただけです。comptime は、修飾子式をコンパイル時に実行する修飾子で、式の中でコンパイル時に未定な値が参照されると、エラーとなります。ここでは、数リテラル同士の商を求めているので、コンパイル時値が確定できます。
_ = ary は、「未使用変数」をサプレッスするときのイディオムです。

テスト 編集

Zigは、言語仕様とツールチェインの両方でテストをサポートしています。

構文(EBNF)
test-decl = [ doc_comment ] "test" [ STRINGLITERALSINGLE ] block
[20]
if.zig
const std = @import("std");
const expect = std.testing.expect;

test "if expr" {
    const f = true;
    var x: usize = 5;
    x += if (f) 10 else 20;
    try expect(x == 15);
}

test "if stmt" {
    const f = true;
    var x: isize = 10;
    if (!f) {
        x += 10;
    } else {
        x -= 20;
    }
    try expect(x == -10);
}
コマンドライン
% zig test if.zig
All 2 tests passed.
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.10.0
% zig test fdiv-inf-nan.zig
Test [3/3] test "fdiv 3"... FAIL (TestUnexpectedResult)
/usr/local/lib/zig/std/testing.zig:303:14: 0x204c1b in std.testing.expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/tmp/zig/fdiv-inf-nan.zig:20:5: 0x204d97 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:
zig-cache/o/9ea24daf8934909606f2d1c0cde0e0d0/test /usr/local/bin/zig

変数 編集

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 ] ";"
[20]
実際は const は、型が省略でき、var は、型が省略できず、両方とも初期化が必須ないので[23]
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 で初期化することができます[24]
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 変数の宣言のとき型が省略されると、初期値から型をコンパイラーが決めてくれます(型推論)。

const print = @import("std").io.getStdOut().writer().print;
pub fn main() !void {
    const i = 0;
    try print("i = {}\n", .{i});
}
実行結果
i = 0
ここでは printi が const 変数です。

[TODO:組込み関数 @import() の解説]

var 変数 編集

キーワード var で変数を宣言するときも初期化は必須です。またいつでも値を変更することはできます。 var 変数の宣言では、型を省略することはできません。

const print = @import("std").io.getStdOut().writer().print;
pub fn main() !void {
    var i : isize = 0;
    try print("i = {}\n", .{i});
    i = 12;
    try print("i = {}\n", .{i});
    i *= i;
    try print("i = {}\n", .{i});
}
実行結果
i = 0
i = 12
i = 144
ここでは i が var 変数です。

識別子 編集

変数の識別子は、外部スコープの識別子をシャドーイングすることは許されません[25]

外部スコープの識別子をシャドーイングすることは許されません
const x = 0;
pub fn main() !void {
    var x : isize = 1;
}
コンパイル結果
An error occurred: /tmp/playground248241835/play.zig:3:9: error: local shadows declaration of 'x'
/tmp/playground248241835/play.zig:1:1: note: declared here

識別子は英数字かアンダースコアで始まり、英数字かアンダースコアがいくつでも続くことができます[25]。また、キーワードと重なってはいけません[25]

外部ライブラリーとのリンクなど、これらの要件に適合しない名前が必要な場合は、@""構文を使用することができます。

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";
  1. スペースを含んだ識別子
  2. 数字から始まる識別子
  3. C言語バインドAPI
  4. C言語の外部リンケージで error 関数を外部参照宣言
  5. C言語の外部リンケージで int fstat(fd_t fd, Stat *buf) 関数を外部参照宣言
  6.  
  7.  
  8. スペースを含んだ列挙メンバーを定義
  9.  
  10. スペースを含んだ列挙メンバーを参照
https://ziglang.org/documentation/master/#Identifiers から引用

整数 編集

整数リテラル 編集

構文(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_*
[20]
2進数・8進数・16進数はそれぞれ 0b0o0x を前置します。
他の多くの言語と異なり 0 の次の文字は小文字が必須で、大文字は受け付けません。

実行時整数値 編集

[TODO]

浮動小数点数 編集

浮動小数点数リテラル 編集

構文(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
[20]
10進数のほか、16進数の浮動小数点数リテラルに対応していますが、2進数・8進数の浮動小数点数リテラルには対応していません。
16進数の浮動小数点数リテラルも、指数部は10進数です。

浮動小数点演算 編集

制御構造 編集

Zigは、やや関数型プログラミング言語の影響を受けており、多くの構文が値を持ちます。 Zigの制御構造の多くは式構文と文構文を持ちます(例外は #switch で式構文しかありません)。

以下、Kotlin#条件分岐から、一部の例を移植しました。

分岐 編集

Zigには、#if#switch の2つの分岐構文があります。

if 編集

Zigでは、if は値を分岐する if式 とブロックを分岐する if文 があります。

構文(EBNF)[20]
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 = "+=" | "-=" | "*=" | "/=" ...(略)
if式の例
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    const i = 0;

    if (i == 0)
        try print("zero\n", .{}) // ここに ; があるとエラーになります。
    else
        try print("non zero\n", .{});

    try print(if (i == 0)
        "ゼロ\n"
    else
        "非ゼロ\n", .{});
}
実行結果
zero 
ゼロ
8行目に ; があると if 式がそこで終わってしまい、else と結合できません。ブロックを使えば…
if文に変更
    if (i == 0) {
        try print("zero\n", .{}); // ブロック中ならば ; があってもエラーになりません。
    } else {
        try print("non zero\n", .{});
    }
と ; を使うことができます[26]
条件不成立でelseを省略すると 編集

if式で、条件が成立せずelseを省略されたとき、式の値は void となります。

条件不成立でelseを省略すると
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    try print("if (false) 1 => {}\n", .{ if (false) 1 });
}
実行結果
if (false) 1 => void
条件式に整数を使うと
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    const i = 0;

    if (i) {
        try print("not zero", .{});
    }
}
コンパイルエラー
./main.zig:7:9: error: expected type 'bool', found 'comptime_int'
    if (i) { 
        ^
Zigでは、if に限らず、条件式は、boolオプショナル型あるいはエラーユニオン型でなければいけません。
    if (i != 0) {
とします。
オプショナル型を条件としたif 編集

ifの条件式にはオプショナル型( ?T )を使うことが出来ます。この場合は、通常の値のほか null を想定でき、null に出会った場合は else 節が実行されます。

[TODO:コード例]

エラーユニオン型を条件としたif 編集

ifの条件式にはエラーユニオン型( !T )を使うことが出来ます。この場合は、通常の値のほかエラーコードを想定でき、エラーコードに出会った場合は else |err| 節が実行され、err がエラーコードです。

[TODO:コード例]

switch 編集

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 ]
[20]
switch.zig
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 print("ComptimeInt: {}\n", .{obj}),
            .ComptimeFloat => try print("ComptimeFloat: {}\n", .{obj}),
            .Struct => try print("Struct: {}\n", .{obj}),
            .Bool => try print("Bool: {}\n", .{obj}),
            .Type => try print("Type: {}\n", .{obj}),
            .Null => try print("Null: {}\n", .{obj}),
            else => try 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 の2つの反復構文があります。

while 編集

while は、条件が成立しているあいだ続く式あるいはブロックを繰返します。 if 同様、while 式を反復する while式 とブロックを反復する while文 があります。

構文(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 の構文も参照してください[20]
Zigも、pythonのように else を伴うことのできる while です。
whileの例 編集
while.zig
pub fn main() !void {
    var i: usize = 1;
    while (i < 50) : (i += 1) {
        try print("{}! == {}\n", .{ i, fib(i) });
    }
}

fn fib(n: usize) usize {
    return if (n < 2) n else fib(n - 1) + fib(n - 2);
}

const print = std.io.getStdOut().writer().print;
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 print("{}! == {}\n", .{ i, fib(i) });
    }
    while (i < 50) {
        try print("{}! == {}\n", .{ i, fib(i) });
        i += 1;
    }
と等価で、追加の式はC言語の for (;;) の三項目にあたります。
elseを伴ったwhileの例 編集
while-with-else.zig
const print = std.io.getStdOut().writer().print;
const std = @import("std");

pub fn main() !void {
    var i: usize = 0;
    while (i < 5) : (i += 1) {
        try print("{} ", .{i});
    } else {
        try print("done!\n", .{});
    }
    i = 0;
    while (i < 50) : (i += 1) {
        try print("{} ", .{i});
        if (i == 10) {
            try print("break!\n", .{});
            break;
        }
    } else {
        try print("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の使いどころ
Zigのwhileループは完走すると実行するelse節を置くことができます。

では、このelse節は何に使うのでしょう?

1000以下の素数を求める(フラッグ版)
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 print("{} ", .{i});
    }
}
break で抜けたかをフラッグ is_prime で判断していますが、
1000以下の素数を求める(else版)
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 print("{} ", .{i});
    }
}
フラッグがなくなり簡素になりました。

Zig以外の言語では、Pythonでも else を伴ったループ構文があります。


ラベル付きwhile 編集

whileループにラベルを付けると、ネストしたループ内からのbreakやcontinueから参照できます[27]

ラベル付きwhile
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 print("i = {}, j = {}\n", .{ i, j });
}
実行結果
i = 7, j = 11
オプショナル型を条件としたwhile 編集

ifと同じように、whileループは条件としてオプショナル型の値を受け取り、ペイロードをキャプチャすることができます。null に遭遇した場合、ループは終了します[28]

while 式に |x| 構文がある場合、while 条件はオプショナル型(あるいは次で述べるエラーユニオン型)でなければなりません(この x かキャプチャされたペイロードです)。

オプショナル型を条件としたwhile
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var sum: u32 = 0;
    while (sequence()) |n| {
        sum += n;
    }
    try 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
エラーユニオン型を条件としたwhile 編集

ifと同じように、whileループは条件としてエラーユニオン型の値を受け取り、ペイロードをキャプチャすることができます。エラーコードに遭遇した場合、ループは終了します[29]

while 式に |x| 構文がある場合、while 条件はエラーユニオン型(あるいは前で述べたオプショナル型)でなければなりません(この x かキャプチャされたペイロードです)。

while 式に else |x| 構文がある場合、while 条件にエラーユニオン型が必要です。

エラーユニオン型を条件としたwhile
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var sum: u32 = 0;
    while (sequence()) |n| {
        sum += n;
    } else |err| {
        try print("err = {}\n", .{err});
    }
    try 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
inline while 編集

whileループはインライン化することができる。これにより、ループが展開され、コンパイル時にしかできないこと、例えば、型をファーストクラスの値として使用することなどができるようになります[30]

[TODO:コード例]

for 編集
構文(EBNF)
for-statement = for-prefix block-expr [ "else" statement ]
              | for-prefix assign-expr ( ; | "else" statement )
for-prefix = "for" "(" expr ")" ptr-index-payload
ptr-index-payload = "|" [ "*" ] IDENTIFIER ( "," IDENTIFIER )? "|"

for-expr = for-prefix expr [ "else" expr ]
[20]
for-ary.zig
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    const int = i16;
    const ary = [_]int{ 1, 5, 4, 6, 4, 9 };
    var sum: int = 0;

    for (ary) |n, i| {
        try print("n = {}, i = {}\n", .{ n, i });
        sum += n;
    }
    try print("sum = {}\n", .{sum});

    var ary2 = ary;

    for (ary2) |*r| {
        r.* += 10;
    }
    for (ary2) |n| {
        try 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,
for-str.zig
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    const str = "simple string";

    for (str) |c| {
        try print("{c} ", .{c});
    }
}
実行結果
s i m p l e   s t r i n g
ラベル付きfor 編集

ラベル付きfor( Labeled for )とは、ラベルを伴った for ループでラベルも for 構文の一部です。for ループにラベルを付けると、ネストしたループ内からのbreakやcontinueから参照できます[31]

ラベル付きfor
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var x :usize = undefined;
    var y :usize = undefined;

    loop_top: for ("Hello") |co, i| {
        for ("World") |ci, j| {
            if (co == ci) {
                try print("c = '{c}'({}, {})\n", .{ ci, i, j });
                x = i;
                y = j;
                break :loop_top;
            }
        }
    }
    try print("x = {}, y = {}\n", .{x, y});
}
実行結果
c = 'l'(2, 3) 
x = 2, y = 3
inline for 編集

Forループはインライン化することができます。これにより、ループが展開され、コンパイル時にしかできないこと、例えば、型をファーストクラスの値として使用することなどができるようになります。インライン化されたforループのキャプチャ値とイテレータ値は、コンパイル時既知です[32]

inline for
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var x :usize = undefined;
    var y :usize = undefined;

    loop_top: inline for ("Hello") |co, i| {
        inline for ("World") |ci, j| {
            if (co == ci) {
                try print("c = '{c}'({}, {})\n", .{ ci, i, j });
                x = i;
                y = j;
                break :loop_top;
            }
        }
    }
    try print("x = {}, y = {}\n", .{x, y});
}
実行結果
c = 'l'(2, 3) 
x = 2, y = 3

[TODO:PrimaryTypeExprの説明:型システムで言及すべき?]

関数 編集

Zigでは、サブルーチンやプロシージャーを関数と呼びます。 プログラムのエントリーポイントの main() も関数です。

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は関数宣言の他、変数宣言なども含みますが、関数宣言部分を抜粋しました[20]
関数の例
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    try print("{}\n", .{div(1, 0)});
    try print("{}\n", .{div(0, 0)});
}

fn div(f1: f64, f2: f64) f64 {
    return f1 / f2;
}
実行結果
inf
nan
他のプログラミング言語をご存じの方なら、関数 div() が前方参照になっているのは大丈夫なのか?と思われるかもしれません。
Zigではトップレベルの識別子は処理系が参照解決(とシグネチャーを含めた型の一致の確認)を引受けてくれます。

エラー 編集

Zigでは、エラー( Errors )も1つの型です[33]

エラー集合型 編集

エラー集合型( Error Set Type )は、enum のようなものです。同じエラー名を複数回宣言することは可能で、宣言した場合は同じ整数値が割り当てられます[34]。 コンパイル全体のユニークなエラー値の数が、エラー集合型のサイズを決定するはずです。しかし、いま[35]は u16 になるようにハードコーディングされています。 サブセットからスーパーセットへエラーを強制することができます。

構文(EBNF)
error-set-decl = "error" "{" identifier-list "}"
[20]
エラー集合型の例
const std = @import("std");
const print = std.io.getStdOut().writer().print;

const FileOpenError = error{
    AccessDenied,
    OutOfMemory,
    FileNotFound,
};

const AllocationError = error{
    OutOfMemory,
};

pub fn main() !void {
    try print("AllocationError.OutOfMemory == FileOpenError.OutOfMemory ⇒ {}\n", .{AllocationError.OutOfMemory == FileOpenError.OutOfMemory});
}
実行結果
AllocationError.OutOfMemory == FileOpenError.OutOfMemory ⇒ true
エラー集合和 編集

2つのエラー集合型は、|| 二項演算子で和集合を得ることが出来ます。

グローバルエラー集合型 編集

anyerror は、グローバルエラー集合を参照します。これは、コンパイルユニット全体のすべてのエラーを含むエラー集合です。これは他のすべてのエラー集合の上位セットで、どのエラー集合の下位セットでもありません。

任意のエラー集合をグローバルエラー集合に強制することができ、グローバルエラー集合のエラーを非グローバルエラー集合に明示的にキャストすることができます。この場合、言語レベルのアサートが挿入され、エラー値が宛先のエラー集合に実際に含まれていることが確認されます。

グローバルエラー集合は、コンパイラーがコンパイル時にどのようなエラーが起こりうるかを知ることができないため、一般に避けるべきです。コンパイル時にエラー集合を知っていた方が、生成されるドキュメントや有用なエラーメッセージ(例えば switch で起こりうるエラー値を忘れてしまうなど)に有利です。

エラーユニオン型 編集

エラー集合型と正常型を二項演算子 ! で結合して、エラーユニオン型( Error Union Type )にすることができます。

var error_or_value: AllocationError ! u16 = 10;

エラーユニオン型は、エラー集合型単体よりも頻繁に使用される可能性があります[36]

構文(EBNF)
error-union-expr = suffix-expr ( "!" TypeExpr )?
suffix-expr = "async" PrimaryTypeExpr SuffixOp* FnCallArguments
            |       PrimaryTypeExpr ( SuffixOp | FnCallArguments )*
[20]

演算子 編集

Zigには、演算子のオーバーロードはありません。 Zigのプログラムに演算子を見たとき、それが次の一覧表に示すもので、それ以外のものでないことが保証されます[37]

演算子一覧表 編集

演算子一覧表( Table of Operators )[38]
構文 関連する型 説明 コード例
a + b
a += b
加算
整数の場合、オーバーフローを起こす可能性があります。
オペランドに対してピア型解決を行います。
2 + 5 == 7
a +% b
a +%= b
ラッピング加算
二の補数のラップ動作が保証されています。
オペランドに対してピア型解決を行います。
@as(u32, std.math.maxInt(u32)) +% 1 == 0
a +| b
a +|= b
飽和加算
オペランドに対してピア型解決を行います。
@as(u32, std.math.maxInt(u32)) +| 1 == @as(u32, std.math.maxInt(u32))
a - b
a -= b
減算
整数の場合、オーバーフローを起こす可能性があります。
オペランドに対してピア型解決を行います。
2 - 5 == -3
a -% b
a -%= b
ラッピング減算
二の補数のラップ動作が保証されています。
オペランドに対してピア型解決を行います。
@as(u32, 0) -% 1 == std.math.maxInt(u32)
a -| b
a -|= b
飽和減算
オペランドに対してピア型解決を行います。
@as(u32, 0) -| 1 == 0
-a
符号反転
整数の場合、オーバーフローを起こす可能性があります。
-1 == 0 - 1
-%a
ラッピング符号反転
二の補数のラップ動作が保証されています。
-%@as(i32, std.math.minInt(i32)) == std.math.minInt(i32)
a * b
a *= b
乗算
整数の場合、オーバーフローを起こす可能性があります。
オペランドに対してピア型解決を行います。
2 * 5 == 10
a *% b
a *%= b
ラッピング乗算
二の補数のラップ動作が保証されています。
オペランドに対してピア型解決を行います。
@as(u8, 200) *% 2 == 144
a *| b
a *|= b
* 整数
飽和乗算
オペランドに対してピア型解決を行います。
@as(u8, 200) *| 2 == 255
a / b
a /= b
除算
整数の場合、オーバーフローを起こす可能性があります。
整数の場合、ゼロ除算を起こす可能性があります。
FloatMode.Optimized モードに於いて、浮動小数点数に対してゼロ除算が発生することがあります。
符号付き整数のオペランドは、既知( comptime-known )かつ正でなければなりません。その他の場合は、[[#@divTrunc|@divTrunc]、[[#@divFloor|@divFloor]、または @divExact を代わりに使用してください。
オペランドに対してピア型解決を行います。
10 / 5 == 2
a % b
a %= b
剰余演算
整数の場合、ゼロ除算を起こす可能性があります。
FloatMode.Optimized モードに於いて、浮動小数点数に対してゼロ除算が発生することがあります。
符号付きまたは浮動小数点オペランドは、既知で正の値でなければなりません。それ以外の場合は、代わりに @rem または @mod を使用してください。
オペランドに対してピア型解決を行います。
10 % 3 == 1
a << b
a <<= b
* 整数
左ビットシフト
b は、既知であるか、aのビット数の log2 を持つ型でなければなりません。
1 << 8 == 256
a <<| b
a <<|= b
* 整数
飽和左ビットシフト
@as(u8, 1) <<| 8 == 255
a >> b
a >>= b
* 整数
右ビットシフト
b は、既知であるか、aのビット数の log2 を持つ型でなければなりません。
10 >> 1 == 5
a & b
a &= b
ビット論理積
オペランドに対してピア型解決を行います。
0b011 & 0b101 == 0b001
a | b
a |= b
ビット論理和
オペランドに対してピア型解決を行います。
0b010 | 0b100 == 0b110
a ^ b
a ^= b
ビット排他的論理和
オペランドに対してピア型解決を行います。
0b011 ^ 0b101 == 0b110
~a
ビット反転
~@as(u8, 0b10101111) == 0b01010000
a orelse b
もし anullならば、b("デフォルト値")を返す、そうでなければ,ラップされていない a の値を返す。bnoreturn型の値である可能性があることに注意。
const value: ?u32 = null;
const unwrapped = value orelse 1234;
unwrapped == 1234
a.?
以下に同じ
a orelse unreachable
const value: ?u32 = 5678;
 value.? == 5678
a catch b
a catch |err| b
もし aerrorならば、b("デフォルト値")を返す、そうでなければ,ラップされていない a の値を返す。code>b がnoreturn型の値である可能性があることに注意。 errはエラーであり,式bのスコープ内にある。
const value: anyerror!u32 = error.Broken;
const unwrapped = value catch 1234;
unwrapped == 1234
a and b
論理積
もし afalse ばらば b を評価せずに false を返す。そうでなければ b を返す。
(false and true) == false
a or b
論理和
もし atrue ばらば b を評価せずに true を返す。そうでなければ b を返す。
(false or true) == true
!a
論理否定
!false == true
a == b
ab が等しい場合は true を、そうでない場合は false を返します。オペランドに対してピア型解決を行います。
(1 == 1) == true
a == null
anull の場合は true を、そうでない場合は false を返します。
const value: ?u32 = null;
value == null
a != b
ab が等しい場合は false を、そうでない場合は true を返します。オペランドに対してピア型解決を行います。
(1 != 1) == false
a > b
ab より大きい場合は true を、そうでない場合は false を返します。オペランドに対してピア型解決を行います。
(2 > 1) == true
a >= b
ab より大きいあるいは等しい場合は true を、そうでない場合は false を返します。オペランドに対してピア型解決を行います。
(2 >= 1) == true
a < b
ab より小さい場合は true を、そうでない場合は false を返します。オペランドに対してピア型解決を行います。
(1 < 2) == true
>
a <= b
ab より小さいあるいは等しい場合は true を、そうでない場合は false を返します。オペランドに対してピア型解決を行います。
(1 <= 2) == true
a ++ b
配列の結合
ab が既知の場合のみ使用可能です。
const mem = @import("std").mem;
const array1 = [_]u32{1,2};
const array2 = [_]u32{3,4};
const together = array1 ++ array2;
mem.eql(u32, &together, &[_]u32{1,2,3,4})
a ** b
配列の乗算
ab が既知の場合のみ使用可能です。
const mem = @import("std").mem;
const pattern = "ab" ** 3;
mem.eql(u8, pattern, "ababab")
a.*
ポインターのデリファレンス
const x: u32 = 1234;
const ptr = &x;
ptr.* == 1234
&a
すべて
アドレスを取得
const x: u32 = 1234;
const ptr = &x;
ptr.* == 1234
a || b
エラー集合のマージ
const A = error{One};
const B = error{Two};
(A || B) == error{One, Two}

オーバーフロー 編集

Zigでは、四則演算などの演算子はディフォルトでオーバーフローを検出します。それとは別に、(C言語などのように)ラッピングを行う演算子が別に用意されています。

オーバーフローの例
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var r :i3 = 0;
    while (r < 10) : (r += 1){
        try 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, .{}); 
   ^
符号付き3ビット整数の r を r < 10 の間インクリメントしています。
符号付き3ビット整数は、 -4 ... 3 の値しか表せず 4 になるとオーバーフローします。
C言語などでは、無限ループになりますが Zig ではランタイムに検出されます。

飽和演算 編集

Zigには、四則演算などの演算子に飽和演算( Saturation calculation )バージョンが用意されています。

飽和演算の例
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var i: i5 = 0;
    var sum: i5 = 0;
    while (i < 10) : (i += 1) {
        sum +|= i;
        try 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 で初期化してもエラーにならない

アンラップ 編集

オプショナル型の式から基底型の値を参照することをアンラップを呼びます。

ifを使ったアンラップ
const std = @import("std");
const print = std.io.getStdOut().writer().print;

pub fn main() !void {
    var s: ?[]const u8 = null;

    if (s) |ss| {
        try print("{s}\n", .{ss});
    } else {
        try print("it's null\n", .{});
    }

    s = "abc";

    if (s) |ss| {
        try print("{s}\n", .{ss});
    } else {
        try print("it's null\n", .{});
    }
}
実行結果
it's null
abc

配列 編集

構文(EBNF)
array-type-start = "[" expr [ ":" expr ] "]"
[20]

配列と特殊な演算子 ++ と ** 編集

配列には、特有な演算子が2つあります。

配列のコード例
const print = @import("std").io.getStdOut().writer().print;

pub fn main() !void {
    const ary = [_]i8{ 2, 3, 5, 7 };

    try print("ary == {}{{ ", .{@TypeOf(ary)});
    for (ary) |elm| {
        try print("{}, ", .{elm});
    }
    try print("}}\n", .{});

    try print("ary ++ ary == {}{{ ", .{@TypeOf(ary ++ ary)});
    for (ary ++ ary) |elm| {
        try print("{}, ", .{elm});
    }
    try print("}}\n", .{});

    try print("ary ** 3 == {}{{ ", .{@TypeOf(ary ** 3)});
    for (ary ** 3) |elm| {
        try print("{}, ", .{elm});
    }
    try print("}}\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 ** aryary ++ ary ++ ary と等価です。

多次元配列 編集

多次元配列( Multidimensional Arrays )は、ネストした配列で生成します[39]

多次元配列
const print = @import("std").io.getStdOut().writer().print;

pub fn main() !void {
    var matrix : [16][16]i32 = undefined;

    // 全成分を 0 に
    for (matrix) |*col| {
        for (col.*) |*value| {
                value.* = 0;
        }
    }

    try print("martix == {}{{\n", .{@TypeOf(matrix)});
    for (matrix) |col| {
        try print("  {{ ", .{});
        for (col) |value| {
            try print("{}, ", .{value});
        }
        try print("}},\n", .{});
    }
    try print("}}\n\n", .{});

    // 対角成分を 1 に
    for (matrix) |*col, ci| {
        for (col.*) |*value, ri| {
            if (ci == ri) {
                value.* = 1;
            }
        }
    }

    try print("martix == {}{{\n", .{@TypeOf(matrix)});
    for (matrix) |col| {
        try print("  {{ ", .{});
        for (col) |value| {
            try print("{}, ", .{value});
        }
        try print("}},\n", .{});
    }
    try print("}}\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 で任意の値をセンチネルにできます[40]

センチネル終端配列
const print = @import("std").io.getStdOut().writer().print;

pub fn main() !void {
    const ary = [_:-1]i32{ 1, 4, 9, 16, 25 };
    try print("@TypeOf(ary) ⇒ {}\n", .{@TypeOf(ary)});
    var i: u16 = 0;
    while (i <= ary.len) : (i += 1) {
        try print("{} ", .{ary[i]});
    }
}
実行結果
@TypeOf(ary) ⇒ [5:0]i32
1 4 9 16 25 -1

ベクトル 編集

ベクトルは、bool整数浮動小数点数ポインターのグループで、可能であればSIMD命令を使って並列に操作されます。ベクトル型は、組込み関数 @Vector で作成します[41]

ベクトルは、基本型と同じ組み込み演算子をサポートしています。これらの演算は要素ごとに行われ、入力ベクトルと同じ長さのベクトルを返します。これには以下が含まれます。

算術演算
+, -, /, *, @divFloor, @sqrt, @ceil, @log, など
ビット演算子
>>, <<, &, |, ~, など
比較演算子
<, >, ==, など

スカラー(個々の数値)とベクトルが混在している場合に数学演算子を使用することは禁止されています。Zigでは、スカラーからベクトルへの変換を容易にするために組込み関数@splatが用意されており、ベクトルからスカラーへの変換には組込み関数@reduceと配列インデックスの構文がサポートされています。ベクトルは、長さが既知の固定長の配列との間の代入もサポートしています。

Zigは,組込み関数@shuffleと組込み関数@selectを提供し,ベクトル内やベクトル間の要素の並べ替えを行います.

ターゲットマシンのネイティブ SIMD サイズより短いベクトルに対する操作は、通常は単一の SIMD 命令にコンパイルされます。ある演算がターゲット・アーキテクチャで SIMD をサポートしていない場合、コンパイラーはデフォルトで各ベクトル要素に対して一度に 1 つずつ演算します。Zig は、232-1 までの既知のベクトル長をサポートしていますが、2 の累乗(2-64)が最も一般的です。ただし、現在の Zig では、長すぎるベクトル長 (例えば 220) はコンパイラーがクラッシュする可能性があります。

ベクトル演算
const print = @import("std").io.getStdOut().writer().print;

pub fn main() !void {
    const a = @Vector(4, i32){ 1, 2, 3, 4 };
    const b = @Vector(4, i32){ 5, 6, 7, 8 };

    try print("  {}\n+ {}\n= {}\n\n", .{ a, b, a + b });
    try print("  {}\n- {}\n= {}\n\n", .{ a, b, a - b });
    try print("  {}\n* {}\n= {}\n\n", .{ a, b, a * b });
    try print("  {}\n/ {}\n= {}\n\n", .{ a, b, a / b });
    try 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, 6, 7, 8 } 
= { 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種類のポインターがあります[42]

  • *T - 正確に1つの項目への単一項目ポインター。
    • deref構文をサポート: ptr.*
  • [*]T - 未知数のアイテムへの多項目ポインター。
    • インデックス構文をサポート: ptr[i]
    • スライス構文をサポート: ptr[start..end]
    • ポインター演算をサポート: ptr + x, ptr - x
    • T は既知のサイズでなければならず、anyopaqueや他の opaque型 にはできません。

これらの型は、配列スライスと密接に関係しています。

  • *[N]T - N 個のアイテムへのポインター、配列への単項目ポインターと同じです。
    • インデックス構文に対応: array_ptr[i]
    • スライス構文に対応: array_ptr[start..end]
    • lenプロパティをサポート: array_ptr.len
  • []T - スライス (ファットポインター。[*]T型のポインターと長さを含みます)。
    • インデックス構文に対応: slice[i]
    • スライス構文に対応: slice[start..end]
    • lenプロパティをサポート: slice.len

単一項目ポインター 編集

単項目ポインターを得るには、&xを使用します。

多項目ポインター 編集

[TODO]

volatile 編集

ロードとストアは、副作用がないことが前提です。MMIO (Memory Mapped Input/Output) のように、ロードやストアが副作用を持つべき場合は volatile を使用します[43]

アライメント 編集

それぞれの型にはアライメント( Alignment )があり、その型の値がメモリーからロードされたり、メモリーにストアされたりするとき、メモリーアドレスがこの数で均等に割り切れるようなバイト数になっています。この値は組込み関数 @alignOf を使って知ることができます[44]

アラインメントはCPUアーキテクチャに依存しますが、常に2のべき乗であり、1 << 29 より小さい値です。

Zigでは、ポインター型はアライメント値を持っています。この値が基礎となる型のアライメントと等しい場合、その型は省略することができます。

align 編集

align は、ポインターのアライメントを指定するために使用します。また、変数や関数の宣言の後に使用して、その変数や関数へのポインターのアライメントを指定することができます。

allowzero 編集

allowzeroポインター属性は、ポインターのアドレスがゼロであることを許可します。これは、アドレスゼロがマッピング可能な独立型OSターゲットでのみ必要とされます。nullポインターを表現したい場合は、代わりにオプショナルポインターを使用します。allowzeroを持つオプショナルポインターは、ポインターと同じサイズではありません[45]

const 編集

constポインター属性は、(ポインターではなく)ポインターが参照する値が変更できないことを示します。

センチネル終端ポインター 編集

センチネル終端ポインター( Sentinel Terminated Pointers )。 構文 [*:x]T は、センチネル値によって長さが決定されるポインターを記述します。これにより、バッファオーバーフローやオーバーリードから保護されます[46]

スライス 編集

スライス( Slices )はポインターと長さです。配列とスライスの違いは、配列の長さが型の一部でコンパイル時にわかるのに対して、スライスの長さは実行時にわかることです。どちらも len プロパティでアクセスすることができます[47]

スライスを使ったコード例
const print = @import("std").io.getStdOut().writer().print;

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 print("@TypeOf(slice) ⇒ {}\n", .{@TypeOf(slice)});
    for (slice) |x| {
        try print("{} ", .{x});
    }
}
実行結果
@TypeOf(slice) ⇒ []i32 
3 5 7 11 13

センチネル終端スライス 編集

センチネル終端スライス( Sentinel-Terminated Slices )。 構文[:x]Tは、実行時に既知の長さを持ち、また長さでインデックスされた要素で センチネル値を保証するスライスです。この型は、それ以前にセンチネル要素がないことを保証するものではありません。センチネル終端スライスはlenインデックスへのエレメントアクセスを可能にします[48]

スライスを使ったコード例
const print = @import("std").io.getStdOut().writer().print;

pub fn main() !void {
    const slice: [:0]const u8 = "hello";

    try print("@TypeOf(slice) ⇒ {}\n", .{@TypeOf(slice)});
    for (slice) |ch| {
        try print("{c} ", .{ch});
    }
}
実行結果
@TypeOf(slice) ⇒ [:0]const u8 
h e l l o

コンテナー 編集

Zig は、structenumunionopaque の4つの組込みコンテナー( container )を持ちます。

[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  ")"
コンテナー関連の構文を抜粋[20]

struct 編集

struct(構造あるいは構造型[49])は、フィールドと呼ばれる名前の付いた要素の集まりで、それぞれのフィールドは名前と型を持っています。

struct の定義
const print = @import("std").io.getStdOut().writer().print;

const Complex = struct {
    real: f64,
    imag: f64,
};

pub fn main() !void {
    try print("@TypeOf(Complex) ⇒ {}\n", .{@TypeOf(Complex)});
}
実行結果
@TypeOf(Complex) ⇒ type
3-6 行目が struct の宣言で、変数に保存しています。
struct で宣言した値の型は type です。
ところで Zigはフィールドの順番とサイズを保証しません。しかし、フィールドはABIアラインされていることが保証されています[50]
もし、順番とサイズを保証したい場合キーワード packed を前置します。
順番とサイズを保証したい場合
const Complex = packed struct {
    real: f64,
    imag: f64,
};
インスタンスの生成
const print = @import("std").io.getStdOut().writer().print;

const Complex = struct {
    real: f64,
    imag: f64,
};

pub fn main() !void {
    const cplx = Complex{
        .real = 1.2,
        .imag = 4.1,
    };
    try print("{}\n", .{ cplx });
}
実行結果
Complex{ .real = 1.2e+00, .imag = 4.1e+00 }
メンバー名の前に . が付くのが独特です。
メンバーは常に pub です。
private や protected や friend はありません。
メソッド 編集

struct はメソッド( Methods )を持つことができます。 メソッドは特別なものではなく、名前空間を持つだけです。 ドットシンタックス(インスタンス名.メソッド名)で呼び出すことができる関数です。

メソッドの例
const print = @import("std").io.getStdOut().writer().print;
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 print("cplx = {}\n", .{cplx});
    try print("cplx.abs() = {}\n", .{cplx.abs()});
    try 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]

匿名structリテラル 編集

Zigでは、structリテラルの型は識別子と結びついている必要はありません。結果が強制される場合、structリテラルはコピーなしで結果の場所を直接インスタンス化します。 この様に、識別子と結びついていないstructリテラルを、匿名structリテラル( Anonymous Struct Literals )あるいはタプル( Tuple )と呼びます[51]

enum 編集

Zigでは、重複しない識別子の集合を表現する型 enum が用意されています[52]

enum の定義
const print = @import("std").io.getStdOut().writer().print;

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] = @intToEnum(Self, i);
       }
       return result;
    }
};

pub fn main() !void {
    try print("@TypeOf(Colour) ⇒ {}\n", .{@TypeOf(Colour)});
    const c = Colour.green;
    try print("c ⇒ {}\n", .{c});
    try print("JISC5062.allCases() ⇒ {s}\n", .{JISC5062.allCases()});
    for (JISC5062.allCases()) |e| {
        try print("{s} ⇒ {}\n", .{ @tagName(e), @enumToInt(e) });
    }
}
実行結果
@TypeOf(Colour) ⇒ type 
c ⇒ Colour.green 
JISC5062.allCases() ⇒ { JISC5062.black, JISC5062.brown, JISC5062.red, JISC5062.orange, JISC5062.yellow, JISC5062.green, JISC5062.blue, JISC5062.violet, JISC5062.gray, 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 で重複していますが、コンテナーは名前空間として機能するので問題になりません。

union 編集

union は、ある値が取り得る型の集合をフィールドのリストとして定義します[53]。 一度にアクティブにできるフィールドは1つだけです。union のメモリー内表現は保証されません。 union はメモリーの再解釈に使用できません。その場合は、組込み関数[[#@ptrCast|@ptrCast] を使用するか、メモリー内レイアウトが保証されている extern union または packed union を使用します。 非アクティブフィールドへのアクセスは安全が確認された未定義動作です。

非アクティブフィールドを参照するとコンパイルエラーになります
const std = @import("std");
const print = std.io.getStdOut().writer().print;

const SimpleUnion = union {
    char: u8,
    int: i64,
    float: f64,
};

pub fn main() !void {
    var su = SimpleUnion{ .char = 'C' };
    try print("su.int = {}\n", .{su.int});
}
コンパイル結果
panic: access of inactive union field
アクティブフィールドであれば参照できます
const std = @import("std");
const print = std.io.getStdOut().writer().print;

const SimpleUnion = union {
    char: u8,
    int: i64,
    float: f64,
};

pub fn main() !void {
    var su = SimpleUnion{ .char = 'C' };
    try print("su.char = {c}\n", .{su.char});
    su = SimpleUnion{ .int = 42 };
    try print("su.int = {}\n", .{su.int});
    su = SimpleUnion{ .float = 2.71828_18284_59045_23536_02874_71352 };
    try print("su.float = {}\n", .{su.float});
}
実行結果
su.char = C
su.int = 42 
su.float = 2.718281828459045e+00
タグ付きunion 編集

union はenumタグタイプを伴って宣言することができます。これにより unionは、タグ付きunion( Tagged union )になり、switch式で使用することができるようになります。タグ付きunionは、そのタグ型に強制されます[54]

タグ付きunion
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 print("@as(Tag, tu) ⇒ {}\n", .{@as(Tag, tu)});
    switch (tu) {
        Tag.char => |ch| try print("ch = {c}\n", .{ch}),
        Tag.int => |i| try print("i = {}\n", .{i}),
        Tag.float => |f| try print("f = {}\n", .{f}),
    }
}
実行結果
@as(Tag, tu) ⇒ Tag.char 
ch = C
タグの enum と、それを使うタグ付きunionのメンバー集合は一致していないとエラーになります。
タグ付きunionを式とするswitch式では、バターンでタグのメンバーを網羅している必要があります(網羅性の検証が行えます。ただし _ や else をパターン使うと台無し)。
匿名enum版タグ付きunion
const std = @import("std");
const print = std.io.getStdOut().writer().print;

const TaggedUnion = union(enum) {
    char: u8,
    int: i64,
    float: f64,
};

pub fn main() !void {
    var tu = TaggedUnion{ .char = 'C' };
    try print("@tagName(tu) ⇒ {s}\n", .{@tagName(tu)});
    switch (tu) {
        .char => |ch| try print("ch = {c}\n", .{ch}),
        .int => |i| try print("i = {}\n", .{i}),
        .float => |f| try print("f = {}\n", .{f}),
    }
}
匿名enum版タグ付きunionは、enum のメンバーと union のメンバーの名前を一致させる手間が不要です。
他方、(unionのメンバーでなく)enum のメンバーに値を与えたいときは、実体化したタグをつか合う必要があります。

構文木を考えてみましょう。Cで実装するとノードの種別と種別ごとのペイロードの共用体になります。Zigではこれを一般化してタグ付きunion1つで実装することが出来ます。 タグ付きunionを使うと網羅性の保証もでき、多くの特性をメソッドとして記述できます。たとえばキーワードや演算子を追加した場合、対応するswitchのパターンがないとエラーになりコンパイル時に事前に変更必要箇所を確認できます。

switch式でタグ付きunionの値の変更 編集

switch式でタグ付きunionのペイロードを変更するには、変数名の前に*を置き、ポインターにします

switch式でタグ付きunionの値の変更
const std = @import("std");
const print = std.io.getStdOut().writer().print;

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 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 print("tu.int ⇒ {}\n", .{tu.int});
}
実行結果
@as(Tag, tu) ⇒ Tag.int 
tu.int ⇒ 52
unionのメソッド 編集

union も他のコンテナーと同様にメソッドを持つことが出来ます。

[TODO:コード例]

extern union 編集

[TODO:コード例] [55]

packed union 編集

[TODO:コード例] [56]

匿名unionリテラル 編集

[TODO:コード例]

Anonymous Union Literals [57]

opaque 編集

「TODO:訳語未定:キーワードはそのままにすべき?]

ブロック 編集

ブロックは、変数宣言の範囲を限定するために使用されます。

構文(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 ";"
[20]

ブロックの値 編集

ブロックは式です。ラベルを付けると、break はブロックから値を返すために使うことができます。

const print = @import("std").io.getStdOut().writer().print;

pub fn main() !void {
    var y: i32 = 123;
    const x = blk: {
        y += 1;
        break :blk y + 2;
    }; 
    try print("x = {}, y = {}\n", .{x, y});
}
実行結果
x = 126, y = 124
ラベル blk は、任意の識別子に置換えられますが、慣習として blk がよく用いられます。

シャドーイング 編集

識別子は、同じ名前を使用して他の識別子を "隠す"ことは決して許されません。 コンパイラーは、外部スコープで既に使われている識別子を、ブロック内で使うとエラーにします。

このため、Zigのコードを読むときには、その識別子が定義されたスコープ内では常に同じ意味であることを確認することができます。ただし、スコープが分かれている場合(入れ子関係にない場合)には同じ名前を使用することができます。

defer 編集

defer は、スコープを抜けるときに実行される式またはブロックを登録します[58]。 複数の defer 文で登録された場合、登録された逆の順序に実行されます。

const std = @import("std");
const print = std.io.getStdOut().writer().print;

fn basic_example() !usize {
    var x: usize = 0;

    {
        defer x = 123;
        x = 0;
        try print("@small block: x = {}\n", .{x});
    }
    try print("@function block: x = {}\n", .{x});

    x = 5;
    return x;
}

fn multiple_example() !void {
    try print("multiple_example(): ", .{});

    defer {
        try print("1 ", .{});
    }
    defer {
        try print("2 ", .{});
    }
    if (false) {
        // defer 自身が一度も実行されない場合は、実行されません。
        defer {
            try print("3 ", .{});
        }
    }
}

pub fn main() !void {
    try print("basic_example()  = {}\n", .{basic_example()});
    multiple_example();
}
実行結果
@small block: x = 0 
@function block: x = 123 
basic_example()  = 5 
multiple_example(): 2 1

errdefer 編集

errdefer は、エラーが原因でスコープを抜けるときに実行される式またはブロックを登録します[59]。 複数の errdefer 文で登録された場合、登録された逆の順序に実行されます。 errdefer は、スコープを抜ける原因となったエラーコードをキャプチャーできます。

    errdefer |err| {
        std.debug.print("the error is {s}\n", .{@errorName(err)}); 
    }

キャスト 編集

[TODO:Zig は強い型付け(); 型強制 (Type coercion) について]

型キャスト( type cast )は、ある型の値を別の型に変換するものです。Zigには、完全に安全で曖昧さのないことが分かっている変換のための型強制( Type Coercion )と、偶然に起こって欲しくはない変換のための明示的キャスト( Explicit Casts )があります。また、複数のオペランド型から結果の型を決めなければならない場合のために、ピア型解決( Peer Type Resolution )と呼ばれる第3の型変換があります[60]

型強制 編集

型強制( Type Coercion )

明示的キャスト 編集

明示的キャスト( Explicit Casts )

ピア型解決 編集

ピア型解決( Peer Type Resolution )は、次のような場所で発生します[61]

  • swicth スイッチ式
  • if式
  • while式
  • for式
  • ブロック内の複数のbreak文
  • いくつかのバイナリー演算

ピア型解決は、ピア型がすべて型強制することができる型を選択します。

ゼロビット型 編集

ゼロビット型( Zero Bit Types )。一部の型では、組込み関数[[#@sizeOf|@sizeOf]は0です[62]

  • void
  • 整数のu0とi0。
  • len == 0 または 0 ビット型の要素型を持つ配列ベクトル
  • タグが1つしかないenum
  • すべてのフィールドがゼロビット型であるstruct
  • ゼロビット型のフィールドを1つだけ持つunion

これらの型は1つの値しか持つことができないので、0ビットで表現する必要があります。これらの型を使用するコードは、最終的に生成されるコードには含まれません。

アセンブリ言語との連携 編集

[TODO:所謂インラインアセンラ]

atomic 編集

非同期関数 編集

Zigでは、キーワード async を伴って関数またはメソッドを呼び出すと、その関数またはメソッドの処理を休止し再開することができます。

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

unreachable 編集

Debug と ReleaseSafe モード、および zig test を使用する場合、unreachable は到達不能なコードに到達したメッセージとともに panic への呼び出しを出します[63]

ReleaseFast モードでは、オプティマイザーは到達不能なコードは決してヒットしないという仮定を使用して最適化を実行します。しかし、ReleaseFastモードでもzigテストはunreachableをpanicへの呼出しとして出力します。

コンパイル時 編集

unreachableの型はnoreturnです[64]。 @TypeOf(unreachable)はコンパイルに失敗します。 unreachable式はコンパイルエラーになるからです。

noreturn型 編集

noreturnは、つぎ文の型です[65]

  • break
  • continue
  • return
  • unreachable
  • while (true) {}

if節やswitchの分岐先( prongs )など、型を一緒に解決する場合、noreturn型は他のすべての型と互換性があります。

組込み関数 編集

組込み関数( Builtin Functions )はコンパイラーが提供するもので、@ が先頭に付きます。パラメーターのキーワード comptime は、そのパラメーターがコンパイル時に既知でなければならないことを意味します[66]

組込み関数一覧
関数プロトタイプ 種別 説明
@addWithOverflow(comptime T: type, a: T, b: T, result: *T) bool
数値演算 result.* = a + b を実行します。オーバーフローまたはアンダーフローが発生した場合、resultにオーバーフローしたビットを格納し、trueを返します。オーバーフローまたはアンダーフローが発生しない場合、falseを返します[67]
@alignCast(comptime alignment: u29, ptr: anytype) anytype
アライメント ptr は *T, fn(), ?*T, ?fn(), または []T のいずれかです。これは ptr と同じ型を返しますが、アライメントが新しい値に調整されています。

ポインターのアラインメントが約束通りかどうかを確認するために、生成されたコードにポインターのアラインメント安全検査が追加されています[68]

@alignOf(comptime T: type) comptime_int
アライメント この関数は、現在のターゲットがCのABIに適合するために、この型がアライメントされるべきバイト数を返します。ポインターの子供の型がこのアライメントを持っている場合、アライメントを省略することができます[69]
@as(comptime T: type, expression) T
キャスト 型強制を行います。このキャストは、変換が曖昧でなく安全である場合に許可され、可能な限り、型間の変換に好ましい方法とされています[70]
@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: anytype) anyframe->T
非同期処理 関数ポインターに対して非同期呼び出しを実行します(非同期関数であってもなくてもかまいません)。

提供される frame_buffer は、関数のフレーム全体を収めるのに十分な大きさでなければなりません。このサイズは @frameSize で決定することができます。小さすぎるバッファを提供すると、安全が確認された未定義の動作が発生します。 result_ptr はオプションです(null を指定することもできます)。提供された場合、関数コールはその結果を結果ポインターに直接書き込み、await が完了した後にそれを読むことができるようになります。await に提供された結果は、result_ptr で示された場所にコピーされます。 [71]

@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T
不可分操作 ポインターをアトミックにデリファレンスしてその値を返します。

T は、ポインターbool整数浮動小数点数あるいは enum でなければなりません[72]

@atomicRmw(comptime T: type, ptr: *T, comptime op: builtin.AtomicRmwOp, operand: T, comptime ordering: builtin.AtomicOrder) T
不可分操作 メモリーをアトミックに変更した後、以前の値を返します[73]

T は、ポインターbool整数浮動小数点数あるいは enum でなければなりません。

サポートされている操作
  • .Xchg - オペランドを変更せずに保存します。enum、整数および浮動小数点数をサポートします。
  • .Add - 整数に対して、2 の補数ラップアラウンド加算を行います。浮動小数点数もサポートしています。
  • .Sub - 整数に対して、2 の補数のラップアラウンド減算を行います。浮動小数点数もサポートしています。
  • .And - ビット単位の論理積
  • .Nand - ビット単位の論理積の反転
  • .Or - ビット単位の論理和
  • .Xor - ビット単位の排他的論理和
  • .Max - オペランドが大きい場合に、オペランドを格納します。整数および浮動小数点数をサポートします。
  • .Min - 小さければ、オペランドを格納します。整数と浮動小数点数をサポートします。
@atomicStore(comptime T: type, ptr: *T, value: T, comptime ordering: builtin.AtomicOrder) void
不可分操作 値をアトミックに保存します。

T は、ポインターbool整数浮動小数点数あるいは enum でなければなりません[74]

@bitCast(comptime DestType: type, value: anytype) DestType
キャスト ある型の値を別の型に変換します[75]
  • sizeOf(@TypeOf(value)) == @sizeOf(DestType) の保証
  • typeInfo(DestType) != .Pointerであることの保証

これが必要な場合は@ptrCast@intToPtrを使ってください。

例えばこんなことに使えます。

  • f32をu32ビットに変換する
  • i32をu32に変換し、2補数を保持する。

コンパイル時に値が既知であれば、コンパイル時に動作します。つまり、専用のキャスト用組込み関数(enum、ポインター、エラー集合型)を持つ型からの制限に加えて、struct、エラーユニオン、スライス、オプショナル型、その他メモリーレイアウトが明確に定義されていない型も、この操作に使用できないことを意味しています。

@bitOffsetOf(comptime T: type, comptime field_name: []const u8) comptime_int
オフセット フィールドのビットオフセットを、それを含む struct からの相対値で返します。

パックされていない struct の場合、これは常に 8 で割り切れる値になります。packed struct の場合、非バイトアラインのフィールドはバイトオフセットを共有しますが、ビットオフセットはそれぞれ異なります[76]

@boolToInt(value: bool) u1
キャスト true を @as(u1, 1) に、false を @as(u1, 0) に変換します。

コンパイル時に値が分かっている場合、戻り値の型は u1 ではなく comptime_int になります[77]

@bitSizeOf(comptime T: type) comptime_int
特性 型がパックされたstruct/unionのフィールドであった場合に、Tをメモリーに格納するために必要なビット数を返します。この結果は、ターゲット固有のコンパイル時定数です。

この関数は、実行時にサイズを測定する。comptime_intやtypeのような実行時に許可されない型については、結果は0を返します[78]

@breakpoint()
デバッグ プラットフォーム固有のデバッグトラップ命令を挿入し、デバッガがそこでブレークするようにします。

この関数は、関数スコープ内でのみ有効です[79]

@mulAdd(comptime T: type, a: T, b: T, c: T) T
数値演算 積和演算( Fused multiply add )は、(a * b) + c に似ていますが、丸めが一度だけしか行われないためより正確です。

浮動小数点数と浮動小数点数のベクトルをサポートしています[80]

@byteSwap(operand: anytype) T
エンディアン操作 @TypeOf(operand)は、整数型またはビット数が8で均等に割り切れる整数型ベクトル型でなければなりません。

operand は、整数またはベクトルでなければなりません。 整数のバイトオーダーを入れ替えます。これは、ビッグエンディアン整数をリトルエンディアン整数に変換し、リトルエンディアン整数をビッグエンディアン整数に変換します。 エンディアンを考慮したメモリーレイアウトのためには、整数の型は @sizeOf bytes で報告されるバイト数に関係する必要があることに注意してください。これはu24で実証されている。つまり、メモリーに格納されたu24は4バイトかかり、この4バイトがリトルエンディアンとビッグエンディアンのシステムでスワップされるものであることを意味します。一方、Tがu24と指定されている場合は、3バイトしか反転されません[81]

@bitReverse(integer: anytype) T
ビット操作 TypeOf(anytype) は、任意の整数型または整数ベクトル型を受け付けます[82]

整数値のビットパターンを反転します(符号ビットがある場合はそれも含めて)。

例えば、0b10110110 (u8 = 182, i8 = -74) は、0b01101101 (u8 = 109, i8 = 109) になります。

@offsetOf(comptime T: type, comptime field_name: []const u8) comptime_int
オフセット フィールドのバイトオフセットを、それを含む struct の先頭からの相対値で返します[83]
@call(options: std.builtin.CallOptions, function: anytype, args: anytype) anytype
関数呼出し 括弧で囲まれた式を呼び出すのと同じように、関数を呼び出します[84]

@call は、通常の関数呼出し構文よりも柔軟性があります。

@cDefine(comptime name: []u8, value)
C言語API この関数は @cImport の内部でのみ使用可能です。これは、#define $name $value を @cImport の一時バッファに追加します[85]
@cDefine("_GNU_SOURCE", {})
#define _GNU_SOURCE
に相当します。
@cImport(expression) type
C言語API この関数は、C言語で書かれたコードを解析し、関数、型、変数、互換マクロ定義を新しい空の struct 型にインポートし、その型を返します[86]

式はコンパイル時に解釈されます。組込み関数の @cInclude, @cDefine, @cUndef はこの式の中で動作し、C言語で書かれたコードとしてパースされる一時バッファに追加されます。

通常、アプリケーション全体で @cImport は 1 つだけにしてください。コンパイラーが clang を複数回呼び出す手間を省き、インライン関数が重複するのを防ぐことができるからです。

複数の @cImport 式を持つ理由としては、以下のようなものが考えられます。

  • シンボルの衝突を避けるため,例えば foo.h と bar.h の両方が #define CONNECTION_COUNT の場合
  • プリプロセッサの定義が異なるC言語のコードを解析する場合
@cInclude(comptime path: []u8)
C言語API この関数は @cImport の内部でのみ使用可能です。これは c_import の一時的なバッファに #include <$path></path> を追加します[87]
@clz(operand: anytype)
ビット操作 整数の最上位(ビッグエンディアンの意味でのリーディング)ゼロの数を数えます[88]

TypeOf(operand)は、整数型または整数型ベクトル型でなければなりません。 operandがコンパイル時既知な整数の場合、戻値の型は comptime_int です。それ以外の場合、戻値は符号なし整数または符号なし整数のベクトルで、整数型のビット数を表すことができる最小のビット数です。 operandが0の場合、整数型Tのビット幅を返します。

@cmpxchgStrong(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T
不可分操作 強い不可分操作として比較交換操作を行います[89]
  • ループ内で cmpxchg を使用する場合は、機械命令でより効率的に実装できる @cmpxchgWeak を選択する方がよいです。
  • Tはポインタ、bool、float、integer、enumのいずれかでなければなりません。
  • typeInfo(@TypeOf(ptr)).Pointer.alignment は >= @sizeOf(T) でなければなりません。
@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T
不可分操作 弱い不可分操作として比較交換操作を行います[90]
  • ループ内でcmpxchgを使用する場合は、散発的な障害は問題なく、機械命令で効率的に実装できる[[#@cmpxchgWeak|@cmpxchgWeak]の方が適しています。しかし、より強力な保証が必要な場合は、[[#@cmpxchgStrong|@cmpxchgStrong]を使用してください。
  • Tはポインタ、bool、float、integer、enumのいずれかでなければなりません。
  • typeInfo(@TypeOf(ptr)).Pointer.alignment は >= @sizeOf(T) でなければなりません。
@compileError(comptime msg: []u8)
コンパイル時 この関数が意味解析されると、msgというメッセージでコンパイルエラーになります。

コードが意味論的なチェックを受けないようにする方法はいくつかあります。 例えば、コンパイル時の定数を使った ifswitchcomptime 関数などがあります[91]

@compileLog(args: ...)
コンパイル時 この関数は、コンパイル時に渡された引数を表示します。

コンパイルログ文を誤ってコードベースに残さないようにするため、コンパイルログ文を指すコンパイルエラーがビルドに追加されます。このエラーによってコードが生成されなくなりますが、それ以外に解析に支障をきたすことはありません。 この関数は、コンパイル時に実行されるコードに対して「printfデバッグ」を行うために使用することができます[92]

@ctz(operand: anytype)
ビット演算 @TypeOf(operand)は、整数型または整数型ベクトル型でなければなりません[93]

この関数は、整数の最下位(ビッグエンディアンの意味での末尾)の0の数を数えます。 オペランドが comptime-known な整数の場合、戻り値の型は comptime_int です。それ以外の場合、戻り値は符号なし整数または符号なし整数のベクトルで、整数型のビット数を表すことができる最小のビット数である。 オペランドが0の場合、[[#@ctz|@ctz]は整数型Tのビット幅を返します。

@cUndef(comptime name: []u8)
C言語API この関数は @cImport の内部でのみ発生します。

これは、[[#@cImport|@cImport] 一時バッファに #undef $name を追加します[94]

@divExact(numerator: T, denominator: T) T
数値演算 正確な除算。呼び出し側は、分母 != 0 かつ @divTrunc(numerator, denominator) * denominator == numerator を保証します[95]
  • divExact(6, 3) == 2
  • divExact(a, b) * b == a.

考えられるエラーコードを返す関数には @import("std").math.divExact を使用します。

@divFloor(numerator: T, denominator: T) T
数値演算 フロアー型除算。負の無限大に丸めます。符号なし整数の場合は、分子 / 分母と同じです。呼び出し側は,分母 != 0 かつ !(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1) が保証されます[96]
  • @divFloor(-5, 3) == -2
  • (@divFloor(a, b) * b) + @mod(a, b) == a

考えられるエラーコードを返す関数には @import("std").math.divFloor を使用します。

@divTrunc(numerator: T, denominator: T) T
数値演算 切捨て除算。ゼロに向かって丸める。符号なし整数の場合は、分子 / 分母と同じです。呼出し側は,分母 != 0 かつ !(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1) を保証します[97]。and denominator == -1).
  • @divTrunc(-5, 3) == -1
  • (@divTrunc(a, b) * b) + @rem(a, b) == a

考えられるエラーコードを返す関数には @import("std").math.divTrunc を使用します。

@embedFile(comptime path: []const u8) *const [N:0]u8
前処理 path で指定されたファイルのバイト数に等しい長さの、ヌル終端固定サイズの配列へのコンパイル時定数ポインターを返します。配列の内容は、ファイルの内容で、これは、ファイルの内容を表す文字列リテラルと同じです。

pathは[[#@import|@import]と同様、現在のファイルに対する絶対パスまたは相対パスです[98]

@enumToInt(enum_or_tagged_union: anytype) anytype
キャスト 列挙値をその整数タグ型に変換します。タグ付きunionが渡された場合、タグ値は列挙値として使用されます。

可能な列挙値が1つしかない場合、結果はcomptimeで知られているcomptime_intです[99]

@errorName(err: anyerror) [:0]const u8
エラー処理 エラーの文字列表現を返すします。error.OutOfMem の文字列表現は "OutOfMem" です。

アプリケーション全体で @errorName の呼び出しがない場合、またはすべての呼び出しで err がコンパイル時に既知の値である場合、エラー名テーブルは生成されません[100]

@errorReturnTrace() ?*builtin.StackTrace
エラー処理 バイナリーがエラーリターントレース付きでビルドされ、この関数が、エラーまたはエラーユニオンの戻値の型を持つ関数を呼出す関数内で呼び出された場合、スタックトレースオブジェクトを返し、それ以外の場合は null を返します[101]
@errorToInt(err: anytype) std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)
エラー処理 エラーをエラーの整数表現に変換します[102]

をサポートします。

エラーの整数表現はソースコードの変更に対して安定していないため、このキャストを避けることが一般的に推奨されています。

@errSetCast(comptime T: DestType, value: anytype) DestType
エラー処理 エラー値をあるエラー集合から別のエラー集合に変換します。変換先のエラー集合にないエラーを変換しようとすると、安全保護された未定義動作が発生します[103]
@export(declaration, comptime options: std.builtin.ExportOptions) void
リンケージ 出力オブジェクトファイル内にシンボルを作成します。

宣言は2つのうちの1つでなければなりません。

  • 関数や変数を特定する識別子(x)
  • 関数や変数を特定するフィールドアクセス (x.y)

この組込み関数は、comptimeブロックから呼び出すことで、条件付きでシンボルをエクスポートすることができます。宣言が C言語呼出し規約の関数で、options.linkage が Strong の場合、これは関数で使用される export キーワードと同じです[104]

@extern(T: type, comptime options: std.builtin.ExternOptions) *T
リンケージ 出力オブジェクトファイル内の外部シンボルへの参照を作成します[105]

(執筆中) 編集

組込み関数一覧(執筆中)
関数プロトタイプ 種別 説明
@fence(order: AtomicOrder)
不可分操作 操作の間にhappened-beforeエッジを導入するために使用されます。

AtomicOrderは@import("std").builtin.AtomicOrderで見つけることができます[106]

@field(lhs: anytype, comptime field_name: []const u8) (field)
データー構造 コンパイル時に文字列でフィールドにアクセスします。フィールドと宣言の両方で動作します[107]
@fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8, field_ptr: *T) *ParentType
データー構造 フィールドへのポインタが与えられたとき、構造体のベースポインタを返します[108]
@floatCast(comptime DestType: type, value: anytype) DestType
キャスト ある浮動小数点数型から別の浮動小数点数型に変換する。このキャストは安全であるが,数値の精度が落ちる可能性がある[109]
@floatToInt(comptime DestType: type, float: anytype) DestType
キャスト 浮動小数点数の整数部を目的型に変換します[110]

浮動小数点数の整数部が出力型に収まらない場合は,安全性を確認した上で未定義動作を呼び出します。 n type, it invokes safety-checked Undefined Behavior.

@frame() *@Frame(func)
フレーム 与えられた関数のフレームへのポインタを返す[111]

この型は anyframe->T と anyframe に強制することができ、T はスコープ内の関数の戻り値の型です。 この関数は、中断点( suspension point )をマークしませんが、スコープ内の関数を非同期関数にします。

@Frame(func: anytype) type
フレーム 関数のフレームタイプを返します。これは、非同期関数だけでなく、特定の呼び出し規約のないあらゆる関数で動作します[112]

この型は、例えば、非同期関数フレームをヒープで確保することを可能にします async の戻り値の型として使用するのに適しています。

@frameAddress() usize
フレーム 現在のスタックフレームのベースポインタを返します[113]

この意味はターゲットに依存し、すべてのプラットフォームで一貫しているわけではありません。積極的な最適化により、フレームアドレスはリリースモードでは利用できないかもしれません。 この関数は、関数スコープ内でのみ有効です。

@frameSize(func: anytype) usize
フレーム これは @sizeOf(@Frame(func)) と同じであり、func は実行時既知のものであってもよい[114]

この関数は通常 @asyncCall と共に使用される。

メモリー管理 編集

Zig言語はプログラマーに代わってメモリー管理を行うことはありません。このため、Zigにはランタイムがなく、Zigのコードはリアルタイム・ソフトウェア、OSカーネル、組込み機器、低遅延サーバーなど、多くの環境でシームレスに動作します。その結果、Zigのプログラマーは常に次のような問いに答えられなければなりません[115]

バイトはどこにあるのか?

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 print = std.io.getStdOut().writer().print;

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 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 でインポート可能な汎用アロケーターがあります。しかし、やはり、アロケーターの選び方の指針に従うことが推奨されます。

アロケーターの選び方 編集

どのアロケーターを使うかは、様々な要因によって決まります。以下は、判断のためのフローチャートです[116]

  1. ライブラリーを作っているのですか?この場合、パラメーターとしてアロケーターを受け取り、ライブラリーのユーザーにどのアロケーターを使うかを委ねるのがベストです。
  2. libc をリンクしていますか? この場合、少なくともメインのアロケーターは std.heap.c_allocator が正しい選択だと思われます。
  3. 必要なバイト数の最大値は、コンパイル時に分かっている数で制限されていますか? この場合、スレッドセーフが必要かどうかによって std.heap.FixedBufferAllocator か std.heap.ThreadSafeFixedBufferAllocator を使ってください。
  4. あなたのプログラムはコマンドラインアプリケーションで、基本的な循環パターンを持たずに最初から最後まで実行され(ビデオゲームのメインループやウェブサーバーのリクエストハンドラーなど)、最後にすべてを一度に解放することに意味があるようなものでしょうか?このような場合、このパターンに従うことをお勧めします。
    cli_allocation.zig
    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()を呼び出すと、すべてが一度に解放されます。
  5. ビデオゲームのメインループやウェブサーバのリクエストハンドラのような周期的なパターンの一部でしょうか?例えば、ビデオゲームのフレームが完全にレンダリングされた後や、ウェブサーバーのリクエストが処理された後など、サイクルの終わりにすべてのアロケーションを一度に解放できる場合、std.heap.ArenaAllocator は素晴らしい候補となります。前の箇条書きで示したように、これによってアリーナ全体を一度に解放することができます。また、メモリの上限を設定できる場合は、std.heap.FixedBufferAllocator を使用すると、さらに最適化できることに注意しましょう。
  6. テストを書いていて、error.OutOfMemoryが正しく処理されることを確認したいですか?この場合は std.testing.FailingAllocator を使ってください。
  7. テストを書いていますか?この場合は std.testing.allocator を使ってください。
  8. 最後に、上記のどれにも当てはまらない場合は、汎用のアロケーターが必要です。Zigの汎用アロケーターは、設定オプションのcomptime構造体を受け取り、型を返す関数として提供されている。一般的には、メイン関数に std.heap.GeneralPurposeAllocator をひとつセットアップし、アプリケーションの様々な部分にそのアロケーターやサブアロケーターを渡していくことになる。
  9. アロケーターの実装を検討することもできます。

バイトはどこにあるのか? 編集

Where are the bytes?

”foo” のような文字列リテラルは、グローバル定数データセクション( global constant data section )にあります。このため、文字列リテラルをミュータブルスライスに渡すとエラーになります[117]

文字列リテラルと同様に、コンパイル時に値が分かっているconst宣言は、グローバル定数データセクションに格納されます。また、コンパイル時変数もグローバル定数データセクションに格納されます。

関数内のvar宣言は、その関数のスタックフレームに格納されます。関数から戻ると、関数のスタックフレームにある変数へのポインターは無効な参照となり、その参照解除は未確認の未定義動作となります。

アロケーターの実装 編集

Zig プログラマーは Allocator インターフェースを満たすことで、自分自身のアロケーターを実装することができます。そのためには、std/mem.zig のドキュメントコメントをよく読んで、allocFn と resizeFn を指定する必要があります[118]

インスピレーションを得るために、多くのアロケータの例を見ることができます。std/heap.zig と std.heap.GeneralPurposeAllocator を見てください。

ヒープアロケーションの失敗 編集

多くのプログラミング言語では、ヒープ割り当てに失敗した場合、無条件にクラッシュすることで対処することにしています[119]

Zig のプログラマは、慣習として、これが満足のいく解決策であるとは考えていません。

その代わり、error.OutOfMemoryはヒープ割り当ての失敗を表し、Zigライブラリーはヒープ割り当ての失敗で処理が正常に完了しなかったときはいつでもこのエラーコードを返します。

Linuxなどの一部のOSでは、デフォルトでメモリーのオーバーコミットが有効になっているため、ヒープ割り当ての失敗を処理することは無意味であると主張する人もいます。この理由には多くの問題があります。

  • オーバーコミット機能を持つのは一部のOSだけである。
    • Linuxはデフォルトで有効になっていますが、設定可能です。
    • Windowsはオーバーコミットしません。
    • 組込みシステムはオーバーコミットしません。
    • ホビー用OSはオーバーコミットがあってもなくてもよい。
  • リアルタイムシステムでは、オーバーコミットがないだけでなく、通常、アプリケーションごとの最大メモリ容量があらかじめ決められています。
  • ライブラリーを作成する場合、コードの再利用が主な目的の1つです。ライブラリーの作成において、コードの再利用は重要な目的の一つである。
  • オーバーコミットが有効であることに依存しているソフトウェアもありますが、その存在は数え切れないほどのユーザー体験の破壊の原因となっています。オーバーコミットを有効にしたシステム、例えばLinuxのデフォルト設定では、メモリーが枯渇しそうになると、システムがロックして使えなくなる。このとき、OOM Killer はヒューリスティックに基づき kill するアプリケーションを選択します。この非決定的な判断により、重要なプロセスが強制終了されることが多く、システムを正常に戻すことができないことがよくあります。

再帰 編集

再帰( Recursion )はソフトウェアをモデリングする際の基本的なツールである。しかし、再帰にはしばしば見落とされがちな問題があります[120]

再帰はZigで活発に実験されている分野であり、ここにある文書は最終的なものではありません。0.3.0のリリースノートで、再帰の状況を要約して読むことができます。

簡単にまとめると、現在のところ再帰は期待通りに動作しています。Zigのコードはまだスタックオーバーフローから保護されていませんが、Zigの将来のバージョンでは、Zigのコードからのある程度の協力が必要ですが、そのような保護を提供することが予定されています。

ライフタイムとオーナーシップ 編集

ポインターを指しているメモリーが利用できなくなったときに、ポインターにアクセスしないようにするのは、Zigのプログラマーの責任です。スライスは、他のメモリーを参照するという点で、ポインターの一種であることに注意してください [121]

バグを防ぐために、ポインターを扱うときに従うと便利な規則があります。一般に、関数がポインターを返す場合、その関数のドキュメントでは、誰がそのポインターを「所有」しているかを説明する必要があります。この概念は、プログラマーがポインターを解放することが適切である場合、そのタイミングを判断するのに役立ちます。

例えば、関数のドキュメントに「返されたメモリーは呼び出し元が所有する」と書かれていた場合、その関数を呼び出すコードは、いつそのメモリーを解放するかという計画を持っていなければなりません。このような場合、おそらく関数は Allocator パラメーターを受け取ります。

時には、ポインターの寿命はもっと複雑な場合があります。例えば、std.ArrayList(T).items スライスは、新しい要素を追加するなどしてリストのサイズが次に変更されるまで有効です。

関数やデータ構造のAPIドキュメントでは、ポインターの所有権と有効期限について細心の注意を払って説明する必要があります。所有権とは、ポインターが参照するメモリーを解放する責任が誰にあるかということであり、寿命とは、メモリーがアクセス不能になる時点(未定義動作が発生しないように)を決めることです。

C言語との相互運用 編集

ZigはC言語から独立しており、他の多くの言語とは異なりlibcに依存しませんが、Zigは既存のC言語で書かれたとの相互作用の重要性を認めています[122]

ZigはC言語との相互運用を容易にするために、いくつかの方法を用意しています。

C言語型プリミティブ 編集

以下に示すZigの型はC言語とのABI互換性が保証されており、他の型と同様に使用することができます[123]

C の void 型と相互運用する場合は anyopaque を使用します。

C言語ヘッダーからのインポート 編集

組込み関数 @cImport を使用すると、.h ファイルからシンボルを直接インポートすることができます[124]。 @cImport 関数は、パラメーターとして式を受取ります。この式はコンパイル時に評価され、プリプロセッサー指令の制御や複数の.hファイルをインクルードするために使用されます。

C言語翻訳 CLI 編集

コマンドライン・フラグ 編集
target と -cflags の使用 編集
cImportとtranslate-cの比較 編集

C言語翻訳キャッシュ 編集

翻訳の失敗 編集

C言語マクロ 編集

C言語ポインター 編集

C言語ライブラリーのエクスポート 編集

オブジェクトファイルの混在 編集

キーワード一覧 編集

align
ポインターのアライメントを指定するために使用します。
また、変数や関数の宣言の後に使用して、その変数や関数へのポインターのアライメントを指定することができます。
allowzero
ポインターの属性 allowzero は、ポインターのアドレスが 0 であることを許可します。
and
論理積
anyframe
関数フレームへのポインターを保持する変数の型として使用することができます。
anytype
関数のパラメーターは、型の代わりにanytypeで宣言することができます。型は関数が呼出された場所で推測されます
asm
インラインアセンブリ式を開始します。これにより、コンパイル時に生成される機械語を直接制御することができます。
async
関数呼出しの前に使用することで、関数がサスペンドされたときにそのフレームへのポインターを取得することができます。
await
await の後に提供されるフレームが完了するまで、現在の関数を中断するために使用することができます。await は、ターゲット関数のフレームから返される値を呼出し側にコピーします。
break
ブロックラベルと一緒に使うことで、ブロックから値を返すことができます。また、ループが自然に終了する前に終了させることもできます。
callconv
関数の呼出し規則を変更します。
catch
前の式がエラーと評価された場合に、その式を評価するために使用することができます。catchの後の式は、オプションでエラーコードをキャプチャーすることができます。[TODO:クリーンアップ]
comptime
コンパイル時に変数や関数パラメーターが既知であることを示すために、宣言の前に comptime を使用することができます。また、コンパイル時に式の実行を保証するために使用することもできます。
const
変更できない変数を宣言します。また、ポインターの属性として使用すると、ポインターが参照する値を変更できないことを示す。
continue
ループの中で使うと、ループの最初にジャンプして戻ることができます。
defer
制御フローが現在のブロックから離れるときに実行する式やブロックを登録します。
else
ifswitchwhileforの代替ブランチ( 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
ブロック、文、式の前で使用することができ、サスペンドポイントに到達しないスコープをマークすることができます。
特に、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
structunion、または enum でなければならないオペランドのすべての公開宣言を現在のスコープにインポートするトップレベルの宣言を行います。
var
変更可能な変数を宣言します。
volatile
ポインターのロードやストアに副作用があることを示すために使用されます。また、インラインアセンブリ式を修正して、副作用があることを示すこともできます。
while
booleanoptionalerrorの各ユニオン式を繰返しテストし、それぞれfalsenullerrorと評価された時点でループを停止するために使用されます。


リファレンス篇 編集

附録 編集

環境準備 編集

Zigは、2022年10月の時点で pre-release の段階にあり、インストール手順も何度か変わっているので、まずは https://ziglang.org/learn/getting-started/ を参照し、最新のインストール手順を確認してください。

オンラインコンパイル実行環境 編集

ローカルにコンパイル実行環境を作るのが困難、あるいは手間を掛けずにコンパイルと実行を試してみたい。そんな場合には、オンラインコンパイル実行環境を使うことも検討に値します。

Zig Playground
公式のオンラインコンパイル実行環境です。
フォーマッターも利用できます。
Wandbox
複数のプログラミン言語に対応しており、Zigにも対応しています。
製作したコードを

両方とも test 機能が使えないこと残念です。

パッケージ マネージャー によるインストール 編集

nightly の Zigを使いたい場合でない限り、お使いのOSやディストリビューションでサポートされているパッケージ マネージャーを使ってインストールする事をお勧めします。 また、ローカルのパッケージデータベースをパッケージシステムのリポジトリーと同期を取り、鮮度の高い状態を維持するよう心がけてください。これは、インストール後も同じですし、zigに限ったことでもありません。

Windows
Zig は、Chocolateyパッケージシステムが対応しています。
> choco install zig
macOS
macOS では、HomebrewMacPortsが対応しています。
最新とタグ付けされたリリース
# brew install zig
Git の master ブランチの最新ビルド
# brew install zig --HEAD
MacPorts
# port install zig
FreeBSD
pkg
# pkg install zig
ports
# make -C /usr/ports/lang/zig all install clean
GNU/Linuxのディストリビューション
いわゆるLinuxは、Linux(ここではOSカーネル)と、FSFがHurdカーネルのために設計・開発したGNUユーザーランド(OSの基本機能を提供するソフトウェアの集合)を組合わせたものです。
このように、LinuxカーネルとGNUユーザーランドを組合わせたソフトウェアプラットフォームをGNU/Linuxと呼びます[125]
Fedora 36
# dnf install zig

ソースコードからのビルド 編集

Zig は、ソースコードが Github の https://github.com/ziglang/zig.git で公開されているので、必要に応じてソースコードからビルドすることができます。 Zigは、セルフホスティング[126]なので、最初にバイナリーを入手してブートストラップするか、クロスビルドしたバイナリーを持込むか、パッケージシステムからインストールし、ターゲットでセルフコンパイル出来る状態を作る方法があります(FreeBSDのPortsが行っているのは、まさにこれです)。

ビルド方法こそ頻繁に内容が変わるので、個別具体的な手順は述べませんが、zig はコンパイラーであるとともにツールチェインでもあり、ビルドシステムも内包しているので、

zig build
とすると build.zig ファイルに書かれているレシピにしたがって自動的にビルドが進行します(ストレージとメモリーと時間に余裕を見る必要があります)。

zig コマンド 編集

Zig の処理系 zig という1つのコマンドで、コンパイラーだけでなく、アーカイバー・C/C++コンパイラーやテストフレームワーク・フォーマッター・ビルドツールなどのツールチェインが統合されています。

zig コマンドを tcsh から実行
% uname -a
FreeBSD localhost 13.1-STABLE FreeBSD 13.1-STABLE #0 6480563d0: Thu Aug 25 19:34:52 JST 2022     sakura@localhost:/usr/obj/usr/src/amd64.amd64/sys/SV1G amd64
% zig version
0.10.0
% cat hello.zig
const std = @import("std");
const print = std.io.getStdOut().writer().print;
pub fn main() !void {
    try print("Hello, {s}!\n", .{"world"});
}
% zig run hello.zig
Hello, world!

zig コマンドの runサブコマンドは、当該ソースファイルの(ソースコードの変更やキャッシュ クリアーなど必要性があれば)コンパイルとコンパイルの成果物の実行を行います。これを「インタープリター風」と称することがありますが、二回目からはソースファイルを変更したりキャッシュをクリアーしない限りコンパイルのオーバーヘッドが不要で、キャッシュ内の実行形式が実行されるなど、ビルドツール/ビルドシステムとしての特徴が際立ち、逐次解釈的な要素は希薄(皆無)です。crystali/interactive サブコマンドのように、真に対話的に実行する処理系も既にあるので、ビルドシステムの簡易呼出しとインタープリターを混同すべきではありません。


リソース 編集

公式ドキュメント
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は以下からアクセス可能です。

脚註 編集

  1. ^ Home ⚡ Zig Programming Language”. ziglang.org. 2022年7月17日閲覧。
  2. ^ Introduction to the Zig Programming Language”. andrewkelley.me. 2022年7月17日閲覧。
  3. ^ Releases · ziglang/zig · GitHub”. github.com. 2022年11月15日閲覧。
  4. ^ Zig Documentation(master@2023-07-31)”. ziglang.org. 2023年7月31日閲覧。
  5. ^ Zig Documentation(0.10.1)”. ziglang.org. 2023年7月31日閲覧。
  6. ^ Zig Documentation(0.10.0)”. ziglang.org. 2022年11月15日閲覧。
  7. ^ Zig Documentation(0.9.1)”. ziglang.org. 2022年7月17日閲覧。
  8. ^ Zig Documentation(0.8.1)”. ziglang.org. 2022年7月17日閲覧。
  9. ^ 0.8.0 Release Notes”. ziglang.org. 2022年7月17日閲覧。 “Disable the special casing of {} for u8 slices/arrays. Unless {s} is specified the contents won't be treated as a string.”
  10. ^ Zig Documentation(0.7.1)”. ziglang.org. 2022年7月17日閲覧。
  11. ^ Zig Documentation(0.6.0)”. ziglang.org. 2022年7月17日閲覧。
  12. ^ Zig Documentation(0.4.0)”. ziglang.org. 2022年7月17日閲覧。
  13. ^ Zig Documentation(0.5.0)”. ziglang.org. 2022年7月17日閲覧。
  14. ^ Zig Documentation(0.3.0)”. ziglang.org. 2022年7月17日閲覧。
  15. ^ Zig Documentation(0.2.0)”. ziglang.org. 2022年7月17日閲覧。
  16. ^ Zig Documentation”. ziglang.org. 2022年7月17日閲覧。
  17. ^ printf() に代表される可変引数関数は利便性は高いですが、書式化文字列と引数の型不一致が生じると、スタックフレームを非可逆的に破壊します。これを未然に防ぐことは、コンパイル時の書式化文字列の解析と引数の型情報の照合で可能ですが、コンパイル時のメモリーと計算量(≒時間)の増大に直結します。
  18. ^ Primitive Types
  19. ^ Primitive Values
  20. ^ 20.00 20.01 20.02 20.03 20.04 20.05 20.06 20.07 20.08 20.09 20.10 20.11 20.12 20.13 20.14 Grammarから抜粋。原文はPEG(Parsing Expression Grammar)なので終端子の記法などはアレンジしています。
  21. ^ 21.0 21.1 21.2 String Literals and Unicode Code Point Literals
  22. ^ Escape Sequences
  23. ^ Assignment
  24. ^ undefined
  25. ^ 25.0 25.1 25.2 Identifiers
  26. ^ この場合、文法的には if-expr(if式)ではなく if-statment(if文)になります。
  27. ^ Labeled while
  28. ^ while with Optionals
  29. ^ while with Error Unions
  30. ^ inline while
  31. ^ Labeled for
  32. ^ inline for
  33. ^ Errors
  34. ^ Error Set Type
  35. ^ 0.10.0
  36. ^ Error Union Type
  37. ^ Operators
  38. ^ Table of Operators
  39. ^ Multidimensional Arrays
  40. ^ Sentinel Terminated Arrays
  41. ^ Vectors
  42. ^ Pointers
  43. ^ volatile
  44. ^ Alignment
  45. ^ allowzero
  46. ^ Sentinel Terminated Pointers
  47. ^ Slices
  48. ^ Sentine -Terminated Slices
  49. ^ C言語の struct は、テンプレートの定義を行う構文で型にするためには、追加の typedef が必要で「構造体」がふさわしかったのですが、Zig では正確に型を定義するので「構造」あるいは「構造型」が妥当です。ここでは混乱を避けるためキーワードでもある struct としました。
  50. ^ struct
  51. ^ Anonymous Struct Literals
  52. ^ enum
  53. ^ union
  54. ^ Tagged union
  55. ^ extern union
  56. ^ packed union
  57. ^ Anonymous Union Literals
  58. ^ defer
  59. ^ errdefer
  60. ^ Casting
  61. ^ Peer Type Resolution
  62. ^ Zero Bit Types
  63. ^ unreachable
  64. ^ At Compile Time
  65. ^ noreturn
  66. ^ Builtin Functions
  67. ^ @addWithOverflow
  68. ^ @alignCast
  69. ^ @alignOf
  70. ^ @as
  71. ^ @asyncCall
  72. ^ @atomicLoad
  73. ^ @atomicRmw
  74. ^ @atomicStore
  75. ^ @bitCast
  76. ^ @bitOffsetOf
  77. ^ @boolToInt
  78. ^ @bitSizeOf
  79. ^ @breakpoint
  80. ^ @mulAdd
  81. ^ @byteSwap
  82. ^ @bitReverse
  83. ^ @offsetOf
  84. ^ @call
  85. ^ @cDefine
  86. ^ @cImport
  87. ^ @cInclude
  88. ^ @clz
  89. ^ @cmpxchgStrong
  90. ^ @cmpxchgWeak
  91. ^ @compileError
  92. ^ @compileLog
  93. ^ @ctz
  94. ^ @cUndef
  95. ^ @divExact
  96. ^ @divFloor
  97. ^ @divTrunc
  98. ^ @embedFile
  99. ^ @enumToInt
  100. ^ @errorName
  101. ^ @errorReturnTrace
  102. ^ @errorToInt
  103. ^ @errSetCast
  104. ^ @export
  105. ^ @extern
  106. ^ @fence
  107. ^ @field
  108. ^ @fieldParentPtr
  109. ^ @floatCast
  110. ^ @floatToInt
  111. ^ @frame
  112. ^ @Frame
  113. ^ @frameAddress
  114. ^ @frameSize
  115. ^ Memory
  116. ^ Choosing an Allocator
  117. ^ Where are the bytes
  118. ^ Implementing an Allocator
  119. ^ Heap Allocation Failure
  120. ^ Recursion
  121. ^ Lifetime and Ownership
  122. ^ C
  123. ^ C Type Primitives
  124. ^ Import from C Header File
  125. ^ GNU/Linuxの中核として、商用・非商用を問わず、再配布可能なユーティリティを収集・配布するディストリビューターが登場しました。これらのディストリビューターは、それぞれの配布物(互換性を失いがち)を、ディストリビューションとして区別する必要が生じました(特に、共有ライブラリーの非互換性は目立ち、Linux支持者自身が嫌悪するWindowsのDLL-Hellに酷似しています)。 このようにして、ディストリビューション間の区別がなされたのです(また、ほかにもマーケティング上の理由などもあります)。
    Unixでディストリビューションという言葉は、ソースコードで配布されるBSDのD (distribution) と関連付けられ、一方、非ソースコード指向のGNU/Linuxディストリビューションが、Unix訴訟の間隙を利する形で386 BSDのニッチをに受入れているのは皮肉なことです。
  126. ^ Zigコンパイラーを始めとする、Zigの言語処理系とツールチェインやユーティリティーなどは、Zig自身で書かれています。

外部リンク 編集