「C言語であった問題をとにかく解決する」を目標に掲げるZig言語

小林哲之氏(以下、小林):それでは、始めます。tetsu_kobaと申します。今日はZigの話をしたいと思います。

Zig言語は、けっこう新しい言語なので、初めて聞く方も多いかと思います。「5分でざっと理解するZig言語」というページを作っておいたので、こちらをご覧になってください。

ところでみなさん、解決まで何時間もかかったバグの原因が、実は数秒で直せるような、しょうもないうっかりコーディングミスだったという経験はありませんか? 私は何度もあります。

バグの原因が、しょうもないうっかりコーディングミスだったということを嫁に話しました。途端に泣き崩れる嫁。「俺はうっかりコーディングミスを絶対に許さない! 絶対にだ!」

というふうにZigの作者は思ったのかどうかわかりませんが、Zig言語は、「Cであったいろいろな問題をとにかく解決する」ということを1つの目標に掲げています。

今日は、こういったうっかりコーディングミスを未然に防ぐためのZigの仕組みをいくつか紹介したいと思います。

基本的にパターンはあって、機械的に検出できるものはおおいに利用します。そして、これはうっかり忘れたわけじゃないよという意味で、明示的に指定するということになります。要するに「指差し確認!ヨシ!」ということですね。じゃあ、まいりましょう。

配列の範囲チェックはデフォルトで行われる

まずは、配列の範囲チェックです。これは、だいたい多くの言語で行われていますが、Zigでどうしてもそこのスピードを速くしたいという場合には、あえてこういった指定(@setRuntimeSafety(false);)をすることで、明示的にブロックのみ配列の範囲チェックをオフできます。

変数は宣言時に必ず初期化をする

それから、変数は宣言時に初期化が必要です。必ず初期化をしなければいけません。初期化しない時には、明示的にundefinedを代入することになります。undefinedが代入している値に、そのままその値を読み込むようなコードを書くと、コンパイラが弾いてくれます。

整数型「int」は存在しない

intという整数型はありません。Cの場合、歴史的にintという型があって、ライブラリでもそこら中intが使われちゃっているわけですが、最近の言語、例えばRustなんかもそうですが、サイズと符合の有無を明確にしてきっちり型を分けます。

u8だとか、i32とか、usizeとかですよね。あと、それによってCだと雑にintと書いてしまうような場面でも、きっちりと区別されます。

暗黙の型変換は、サイズが大きくなる方向にのみ許されています。そうでない場合、@truncateという組み込み関数を使って明示的に切り詰めるとか、あるいはキャストをかけるとか、そういったことをしなければいけません。

ちなみにZigでは、このサイズに上限はあるのですが、任意に指定できます。ですので、6bitのunsignedだったらu6、3bitのsignedだったらi3という型も可能です。これはちょっと後から出てきますので覚えておいてください。

算術演算・飽和演算・シフト演算のチェックについて

算術演算です。これはデフォルトでオーバーフローがチェックされます。オーバーフローはわかりにくいバグになるんですよね。(スライドを示して)これは足し算の例ですが、普通に書いた場合、オーバーフローのチェックがされます。オーバーフローすると、そこで異常終了して、スタックトレースが表示されるという感じになります。

今までのC言語のように、ラップアラウンドされるようにしてほしい場合には、そういう演算子が別に用意されていて、「+%」という書き方をします。

それから、飽和演算。要するに上限で止まってほしい時には、(スライドを示して)こういった演算子も用意されています。詳しくは、ドキュメントのOperatorsを見てください。

シフト演算。これはたまたま気がついたのですが、すごいですよ。この関数、コンパイルエラーになるんですよ。

xとyと、両方ともi64の場合なんですが、コンパイルエラーが何を言っているかというと、このyの部分は、0から63までじゃないといけないんですよね。なので、i64という変数だとエラーになる。その0から63まで表す変数の型が、実は6bit。要するにunsigned 6bitなので、u6というそういう型なんですよ。

これはどういうふうにしたらコンパイルが通るかというと、実はこんなふうにします。この関数(​​std.math.Log2Int())を使うとi64の大きさを、シフトできる変数の型を返すんですね。これ、u6という型が返ってきますので、それに対してキャストをかけると、これでコンパイルが通るという感じになります。

これ、本当にチェックが厳しいですね。こんなチェックをするなんて、やはりZigはガチでこういったミスを許さないということがわかります。

switch文はすべての場合を網羅する必要がある

次、switch文はすべての場合を網羅する必要があります。これはswitch文の例ですが、Zigの場合はこういうふうに範囲指定をすることもできます。

その他の場合、いわゆるCでいうところのデフォルトをZigではelseと書くのですが、なんにもしない場合でも必ず書かなきゃいけません。この空っぽのカッコは、なんにもしないという意味なんですが、これを省略するとコンパイルエラーになります。

要するに、このxの値はi32なんですが、i32の範囲のどんな値が来ても必ずどれかを通過するようにしなければいけません。それによって、抜けがないことをコンパイラがしっかりチェックしてくれます。

関数の引数は書き換え不可

次、関数の引数はimmutable。要するに書き換えできないということです。なぜ書き換えできないかという理由は、ドキュメントにきちんと書いてあるのですが、これをするとどんないいことがあるかなとちょっと考えてみました。

もし、オブジェクトを中で書き換えてもらうように参照、要するにポインターで渡すようにするべきところを間違えて実体渡しにするように書いちゃったとします。

そうすると、渡された側ではそれを書き換えるわけなんですが、immutableなものを書き換えようとしたというのでコンパイルエラーになります。

そうすると、間違いに気がつけるんですよね。これに気がつかないと、書き換えたのに元のものというのはコピーなので、変化がないわけです。そういうなんかわけのわからんバグになるところを、コンパイルエラーになることで気がつくことができます。

変数の名前の重複はダメ

次、変数のshadowing禁止。verboseというグローバル変数があるのですが、この関数の中で、このローカル変数で同じ名前のローカル変数を作って、セットしちゃいました。

これって、本当にこれがやりたいことなのかどうなのかがちょっとわかりませんね。実は、グローバル変数を書き換えるつもりだったのに、うっかり手が滑ってここにvarと書いちゃったんじゃないの? という可能性もあるわけですよ。Zigは、そういう紛らわしいことはするなと。名前が被るのはダメとされています。

なので、グローバル変数はあまり使わないほうがいいと思いますし、スコープの広い変数は被りにくいように長い名前にするのがよいかと思います。

ローカル変数、関数の戻り値、関数の引数が未使用だとエラーになる

次です。未使用だとコンパイルエラー。ローカル変数や関数の戻り値が未使用だとエラーになるのは、Go言語でもそうでした。さらにZigでは、関数の引数も未使用だとエラーになります。

この例では、引数としてx、y、zがあって、zを使っていません。使っていない時には使っていませんよということで、こういう行( _ = z;)を挿入しないとコンパイルエラーになります。

これについてツイートをしたところ、たくさんリツイートされて、意外に反響がありました。

いろいろなことを言われたのですが、私の意見を言っておくと、この「未使用変数でコンパイルエラー」には賛成ですし、たぶん今まで見てきたとおり、Zigの開発チームは、これをオン・オフできるようにするとか、そんなふうに日和ったりは絶対にしないと思います。

やはりこういったチェックがされているほうが、第三者が書いたソースコードを読む時にも、脳の負荷が少なくて済みます。トリッキーなことがあるかどうかという疑心暗鬼にならなくて済むわけです。これは品質の底上げになります。

これが必須になっていることで、要するにこのチェックを通っていないソースコードは、Zigのソースコードとして流通しないということを表しているわけです。

常識をアップデートして新しい常識で臨もう

GitHubとかで取ってこられるコードは、基本的にコンパイルが通っているわけで、コンパイルが通っているイコール、こういったチェックを全部通過しているということになります。

確かに試行錯誤する時には煩わしいとは思いますが、この煩わしさを減らすのは、エディタのプラグインの側でなんとか便利にするという方向性のほうがいいかと思います。

AIの時代ですので、賢いプラグインがこれから出てくるかと思います。せっかく新しい言語を使うのであれば、常識をアップデートして新しい常識で臨みましょう、というのが私の意見です。

というわけで、Zig言語はおもしろいですよ。まだまだ話したいネタはいっぱいありますが、今日の発表はこれまでにしたいと思います。

どうもありがとうございました。以上です。

Zig言語にいろいろな人を引き込んでいきたい

司会者:ありがとうございます。みんな反応がすごい(笑)、反応がすごいな。

ちなみに、Zig言語を私は初めて知りました。最近、tetsu_kobaさんがZig言語っていうのをやっているなと「Twitter」で眺めていたんですが、Zig言語を書くきっかけはあるんですか? 使いたいライブラリがそうだったとか?

小林:2022年の夏頃になんか流行ったんですよ。

司会者:あっ、そうなんだ。

小林:「Bun(バン)」というJavaScriptのランタイムの速いのが登場して、それがこのZigという言語で書いてあると。

それまでもZigはあったんだけど、やはり出たばかりの言語は本当に使いものになるのかという不安があるから、あまり手を出さずにいました。でも、そういうものが作れるんだったらもう実用レベルに近いんじゃないかなと思って。

ブームが来た時に私もちょっと調べていて、その時はコードとドキュメントを見ただけだったんですが、12月頃になってから1回ちょっと自分で実際にコードを書いてみたら、すごくおもしろくて。今は、だいぶ時間を使ってZigを調べたりブログの記事に書いたり、そんな感じですね。

なにしろ、まだマイナーなので使っている人が少ないでしょう? だからなるべく、いろいろな人を引き込んでいきたいなと思ってもう一生懸命宣伝しています。みんな、Zigやろうよ(笑)。

司会者:なるほど。「そういう言語だったんだ」という感想をされている方がけっこう多かったので、目的にかなったかなと思います。ありがとうございました。

※なお、この発表のスライドと動画は発表者本人のブログから見ることができるようになっています。 「https://zenn.dev/tetsu_koba/articles/c039b865114b93