すでにC++、D、Rustがあるのに、なぜZigなのか?
隠れた制御フローなし
もしZigのコードが関数を呼び出すために飛び出しているように見えないなら、それは違うのです。つまり、次のコードはfoo()
とbar()
のみを呼び出し、何も型を知らなくてもこれが保証されていることを意味します:
var a = b + c.d;
foo();
bar();
隠れた制御フローの例:
- Dには
@property
関数があり、これはフィールドアクセスのような形で呼び出すメソッドなので、上の例ではc.d
が関数を呼び出すかもしれません。 - C++、D、Rustには演算子のオーバーロードがあるので、
+
演算子は関数を呼び出すかもしれません。 - C++、D、Goには throw/catch 例外があるので、
foo()
が例外をスローしてbar()
が呼び出されないようにすることができます。(もちろん、Zigでもfoo()
がデッドロックしてbar()
が呼ばれなくなることがありますが、これはチューリング完全な言語ならどこでも起こり得ることです)。
この設計上の判断の目的は、可読性を向上させることです。
隠れたアロケーションなし
Zigはヒープ割り当てに関しては、ハンズオフ・アプローチを採用しています。
new
キーワードや、ヒープアロケータを使用する他の言語機能(例: 文字列連結演算子[1])は存在しないです。
ヒープの概念全体は、言語ではなく、ライブラリとアプリケーションのコードによって管理されます。
隠れたアロケーションの例:
- Goの
defer
は、関数ローカルのスタックにメモリを割り当てます。この制御フローが直感的でないことに加えて、ループの中でdefer
を使用すると、メモリ不足で失敗する可能性があります。 - C++のcoroutineは、coroutineを呼び出すためにヒープメモリを確保します。
- Goでは、関数呼び出しによってヒープが確保されることがあります。これは、goroutineが小さなスタックを割り当て、呼び出しスタックが十分に深くなったときにリサイズされるからです。
- Rust標準ライブラリの主要なAPIはメモリ不足でパニックになり、アロケータパラメータを受け取る代替APIは後回しになります(rust-lang/rust#29802 を参照)。
ガベージコレクタはクリーンアップ側で証拠を隠すので、 ほぼすべてのガベージコレクタ言語には隠れたアロケーションが散見されます。
隠れアロケーションの主な問題は、コード片の再利用性を妨げ、コードが展開されるのに適した環境の数を不必要に制限してしまうことです。 簡単に言えば、制御フローや関数呼び出しがメモリ割り当ての副作用を持たないことを保証しなければならないユースケースがあります。 したがって、プログラミング言語は現実的にこの保証を提供できる場合にのみ、 このユースケースに対応することができるのです。
Zigの場合、ヒープアロケータを提供し動作させる標準ライブラリ機能がありますが、 それはオプションの標準ライブラリ機能であり、言語自体に組み込まれているわけではありません。 ヒープアロケータを決して初期化しなければ、プログラムがヒープアロケートをしないことを確信できます。
ヒープメモリを確保する必要がある標準ライブラリの機能はすべて、それを行うためにAllocator
パラメータを受け取ります。
これは、Zig標準ライブラリが独立したターゲットをサポートしていることを意味する。例えば、std.ArrayList
やstd.AutoHashMap
はベアメタルプログラミングに使用することができます!
カスタムアロケーターを使えば、手動でのメモリ管理も楽になります。 Zigにはデバッグアロケータがあり、use-after-freeやdouble-freeに直面しても、メモリの安全性を維持することができます。 メモリリークを自動的に検出してスタックトレースを表示する。 アリーナアロケータがあり、いくつものアロケーションを1つに束ねそれぞれのアロケーションを個別に管理するのではなく、 一度にすべて解放することができます。特殊用途のアロケータは特定のアプリケーションのニーズに合わせて、 パフォーマンスやメモリ使用量を改善するために使用することができます。
[1]:実は文字列連結演算子(一般的には配列連結演算子)はあるのですが、コンパイル時にしか動作しないので、やはり実行時のヒープ確保はしてくれません。
標準ライブラリがない場合のファーストクラスのサポート
上で述べたように、Zigには完全にオプションの標準ライブラリがあります。 各標準ライブラリのAPIは、それを使う場合にのみプログラムにコンパイルされます。 Zigはlibcとリンクすることも、リンクしないことも同じようにサポートします。 Zigはベアメタルやハイパフォーマンスな開発に適しています。
例えばZigでは、WebAssemblyのプログラムは標準ライブラリの通常の機能を利用しながら、 WebAssemblyへのコンパイルをサポートする他のプログラミング言語と比較して、 最も小さなバイナリを生成することができるのです。
ライブラリのためのポータブル言語
プログラミングの聖杯の1つは、コードの再利用です。しかし悲しいことに、実際には何度も何度も車輪の再発明を繰り返していることに気づきます。多くの場合、それは正当化されています。
- アプリケーションにリアルタイム性が要求される場合、ガベージコレクションやその他の非決定的な動作を使用するライブラリは依存対象として不適格となります。
- もし言語がエラーを無視しやすく、ライブラリがエラーを正しく処理しバブルアップすることを確認するのが難しい場合、関連するすべてのエラーを正しく処理したことを知りながら、ライブラリを無視して再実装したくなることがあります。Zigは、プログラマができる最も怠惰なことはエラーを正しく処理することであり、したがって、ライブラリがエラーを適切にバブルアップしてくれるということに、それなりの確信を持てるように設計されています。
- 現在、C言語が最も汎用的で移植性の高い言語であることは、実用的にも正しいことです。Cのコードと相互作用する能力を持たない言語は、曖昧になる危険性があります。Zigは、外部関数をCのABIに準拠させることを容易にすると同時に、実装の中でよくあるバグを防ぐ安全性と言語設計を導入し、ライブラリのための新しいポータブル言語となることを試みているのです。
既存プロジェクトのためのパッケージマネージャとビルドシステム
Zigはプログラミング言語ですが、従来のC/C++プロジェクトの文脈でも有用であることを意図したビルドシステムとパッケージマネージャを同梱しています。
CやC++のコードの代わりにZigのコードを書けるだけでなく、autotools、cmake、make、scons、ninjaなどの代わりとしてZigを使うことができます。その上、ネイティブな依存関係のためのパッケージマネージャを提供する(予定)です。このビルドシステムは、プロジェクトのコードベース全体がCまたはC++であっても適切であることを意図しています。
apt-get、pacman、homebrew などのシステムパッケージマネージャはエンドユーザにとって便利なものですが、開発者のニーズには不十分な場合があります。言語固有のパッケージマネージャは、コントリビュータがいない場合と多い場合の違いになり得ます。オープンソースプロジェクトでは、プロジェクトをビルドすることの難しさは、潜在的コントリビュータにとって大きなハードルとなっています。C/C++のプロジェクトでは、依存関係があることは致命的で、特にWindowsではパッケージマネージャがありません。Zig自体をビルドする場合でさえ、ほとんどの潜在的コントリビュータはLLVMの依存関係で苦労しています。Zigは、プロジェクトがネイティブ・ライブラリに直接依存する方法を提供しています(今後提供する予定です)。
Zigは、プロジェクトのビルドシステムを、宣言的なAPIを使った合理的な言語に置き換え、パッケージ管理も提供し、他のCライブラリに実際に依存できるようにすることを提案している。依存関係を持つことで、より高度な抽象化が可能になり、再利用可能な高レベルのコードの普及が期待できます。
シンプルさ
C++、Rust、Dは機能が多すぎて、作業しているアプリケーションの実際の意味から逸れてしまうことがあります。アプリケーションをデバッグするのではなく、プログラミング言語の知識をデバッグしている自分に気がつくのです。
Zig にはマクロもメタプログラミングもありませんが、それでも複雑なプロ グラムを明確かつ繰り返しのない方法で表現するには十分強力です。Rust でさえ、format!
のような特殊なケースを持つマクロがあり、これはコンパイラ自体に実装されています。一方Zigでは、同等の関数が標準ライブラリに実装されており、コンパイラに特殊なケースのコードはありません。
ツール
Zigはダウンロードセクションからダウンロードすることができます。Zigは、Linux、Windows、macOS、FreeBSD用のバイナリアーカイブを提供しています。以下は、これらのアーカイブの1つで得られるものについて説明しています:
- ダウンロードしたアーカイブを展開することでインストールされ、システム構成は不要
- 静的にコンパイルされているため、ランタイム依存無し
- 成熟し、よくサポートされているLLVMインフラストラクチャを使用し、深い最適化とほとんどの主要なプラットフォームのサポートが可能
- ほとんどの主要なプラットフォームへのクロスコンパイルが可能。
- サポートされているプラットフォームで必要なときに動的にコンパイルされるlibcソースコード同梱
- キャッシュ機能を備えたビルドシステムを搭載
- libcをサポートしたCおよびC++コードをコンパイル可能