※ この記事はZigを初めて触った人が書いた記事です。Zigを初めて触った人はこんな感想を持つんだ、という参考として受け取っていただければ幸いです。

めっちゃ強いOBに「次に流行る言語はなんですかね?」って聞いたらZigって言われたので、この前書いた電卓自作の記事をZigで走ります。

弊環境ではパッケージマネージャにyayを使っているので、インストールはyay -S zigだけで済みます。

1  Introducing Zig – Introduction to Zig を読んだところ、zig init, zig build-exe src/main.zigをすると実行バイナリが出来上がるそうです。makefileとか書く必要がないのは良いですね。最近の言語は大抵そうですが。

zig buildをすると、カレントディレクトリにあるbuild.zigをもとにいい感じにビルドしてくれるみたいです。

見た感じroot.zigにライブラリ的な関数をおいて、main.zigにmain関数を置く感じなのでしょうか?それぞれに対するテストは、コードと同じファイルに置く思想みたいです。コードとテストが近いのはわかりやすくて良さそうです。

ステップ2

数の入力方法がよく分からなかったので、とりあえずステップ2から始めます。書いたコードは以下のような感じになります。

const std = @import("std");
const stdout = std.io.getStdOut().writer();
const stdin = std.io.getStdIn().reader();
 
pub fn main() !void {
    var buffer: [256]u8 = undefined;
    const result = try stdin.readUntilDelimiterOrEof(&buffer, '\n');
    const line = result orelse return;
    try stdout.print("{s}\n", .{line});
}
  • const std = @import("std");なんかはCommonJSみを感じます。
  • const stdout = std.io.getStdOut().writer();という関数を関数の外で呼び出せるのは不思議な感じがします。
  • readUntilDelimiterOrEofの使い方を理解するまで時間がかかりました。
    • バッファ側をいじるんじゃなくて戻り値を使うんですね・・・。
  • tryorelseでエラー処理をするのは結構好きです。Go言語if err != nil { ... }より好きです。
  • (ここで実装したものは入力文字列を全部出力していて、本来のステップ2とは少し異なります。)

ステップ4

const std = @import("std");
const stdout = std.io.getStdOut().writer();
const stdin = std.io.getStdIn().reader();
const testing = std.testing;
 
var index: usize = 0;
 
pub fn main() !void {
    var buffer: [256]u8 = undefined;
    const result = try stdin.readUntilDelimiterOrEof(&buffer, '\n');
    const line = result orelse return;
    try stdout.print("{d}\n", .{readInt(line)});
}
 
fn readInt(str: []const u8) i32 {
    var result: i32 = str[index] - '0';
    index += 1;
    while (str.len > index and str[index] >= '0' and str[index] <= '9') {
        result = result * 10 + str[index] - '0';
        index += 1;
    }
    return result;
}
 
test "readInt" {
    index = 0;
    try testing.expectEqual(0, readInt("0"));
    index = 0;
    try testing.expectEqual(1, readInt("1"));
    index = 0;
    try testing.expectEqual(9, readInt("9a"));
    index = 0;
    try testing.expectEqual(10, readInt("10bc"));
    index = 0;
    try testing.expectEqual(11, readInt("11"));
    index = 0;
    try testing.expectEqual(123, readInt("123"));
    index = 2;
    try testing.expectEqual(3, readInt("123"));
}
  • 細かい実装はさっさと飛ばしてステップ4です。
  • せっかくなのでテストも含めて全部のコードを載せておきます。
  • テストも書きましたが、すべてのファイルのテストを一つのコマンドで纏めて実行する方法が分からなくて調べました。テスト|Zigについてのメモ この記事によると、zig build testで良いとのことです。
  • 電卓自作では入力された文字列をグローバル変数に置いていたのですが、今回のコードではresultがconstなのが気持ちよかったので、各関数にconstで渡していく形にしてみました。
  • 配列の処理について
    • for文で要素を取り出せる構文があるのは良さげに感じました。が、今回のコードでは地道にwhileで処理しています。
    • 配列の長さを配列と一緒に扱えるのは良さげに感じました。テストビルドでは範囲外参照のエラーも出してくれるのはありがたいです。

ステップ9

ステップ13

  • 掛け算と割り算を実装してみました。
  • 割り算をする際に、i32については割り算ができない、というエラーメッセージが出ました。
    • 具体的には、result /= nextをするとerror: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExactというエラーが出ました。
    • Zig の文書読んで所感を記す 2 ZIG - Qiita にどうしてこのエラーが出るのかが解説されています。
  • 差分はステップ13 · soukouki/zig-calc@74813ee · GitHubから確認できます。

ステップ15

全体を通しての感想

今回触った範囲はかなりさわりの部分だけなので、Zig言語全体に対する感想ではありません。特に構造体やスレッド、C言語との相互運用などについては一切触っていません。

  • C言語Go言語の中間みたいな味をしつつ、不便なところは積極的に改善しているように感じました。
    • 標準入力を受け取るときにバッファを用意して〜、というところはC言語みを感じました。
    • Go言語でよく書かされるif err != nilの代わりに、Error!型とtryの組み合わせがあるのは良いと感じました。
  • 配列の範囲外参照について、範囲外アクセスをした瞬間にエラーを出すのは安全側に振っていてありがたいなと感じました。
    • ただ、アクセス範囲を毎回判定する分のオーバーヘッドはどう考えているのかなと不思議に思っています。
  • Zigにはビルドシステムが存在するのはありがたかったです。
    • C言語ではMakefileと毎回戦っているのですが、それが楽になります。
    • 強いて言えば、パッケージマネージャまで同封されていれば、他のモダンな言語のように簡単にライブラリを呼び出せて良いのにな、とは思います。