0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Zig】Zig言語のレビュー

0
Posted at

元: https://technicalsuwako.moe/blog/zig-review

本題に入る前に、お知らせがあります。
最近「C Certified Associate」を取得し、来月にはC++版の同資格も取得予定です。
更に今年中には「C Certified Professional」と「C++ Certified Professional」の取得も目指しています。
又、此れにより本名が明らかになりますが、此の情報は076スタジオが法人化された時点で、法律により既に公開されていました。

此れまでのZigとの付き合いは、正直な所かなり波があります。
うまく動いて「凄い事を成し遂げた」と感じる事もあれば、コンパイラと格闘して諦めてしまう事もあります。
Zigは約11年間開発されていますが、未だ1.0はリリースされていません。
あたしが最後に使ったのはバージョン0.11.0で、其れ以降言語は大きく変わっています。

執筆時点では、バージョン0.16.0が丁度リリースされたばかりです。
試すには絶好のタイミングでしょう。
最初に目につく大きな変更点として、OpenBSDが公式に対応になり、最新バージョンを使う為にポーツツリーからコンパイラをビルドする必要がなくなりました。

Zigとは何か?

C、C++、Rustは誰もが知っていますが、Zigを知っている人はそれほど多くないでしょう。
以前にも説明しましたが、システムプログラミング言語はペアで現れる傾向があります。完全に偶然ですが、興味深い点です。

CとC++はシステムプログラミングの絶対的な王者であり、まるで不滅の存在です。
OdinとJaiはゲーム開発やプログラミングの楽しさに焦点を当てた言語です。
GoとCarbonはWebバックエンドに特化した言語です。
ZigとRustは、より厳格なコンパイラと自己完結型エコシステムを備え、王者の現代的な後継となることを目指す言語です。

RustがC++に相当する存在であるのに対し、ZigはよりCに近い位置付けです。
メモリ安全性については個人的には完全には信用していませんが、どちらもそれを実現すると謳っています。

又、JavaやC#の様なエンタープライズ向け言語もあり、此れらもある程度はシステムプログラミングが可能ですが、純粋なシステムプログラミング言語とは言えません。

尚、ZigコンパイラはZigコードのコンパイルだけでなく、Cコードのコンパイルも可能です。
Zig最大の強みはCライブラリとの相互運用性であり、今回のデモでも其れを活用します。

インストール

どのOSであっても、パッケージマネージャではなく公式サイトからZigコンパイラをダウンロードする事が推奨されます。
多くのLinuxディストリビューションやBSD開発者は、Rust程Zigを追っていない為(リリースサイクルが遅いにも関わらず)、古いバージョンを使う事になる可能性が高いです。

インストールは非常に簡単で、コンパイラが完全に自己完結している点は非常に良いところです。
但しWindowsでは環境変数の設定が追加で必要になります。

Linux・BSD

$ doas su -
# wget https://ziglang.org/download/0.16.0/zig-x86_64-openbsd-0.16.0.tar.xz
# xzcat zig-x86_64-openbsd-0.16.0.tar.xz | tar xf -
# cd zig-x86_64-openbsd-0.16.0
# mv lib /usr/local/lib/zig
# mv zig /usr/local/bin
# cd ..
# rm -rf zig-x86_64-openbsd-0.16.0 zig-x86_64-openbsd-0.16.0.tar.xz

Windows

zig-x86_64-windows-0.16.0.zipC:\\に解凍し、ルートディレクトリの名前をZigに変更します。
詰りZigのルートはC:\\Zigになります。
其の後、Windows + Xでクイックメニューを開き、Yを押してシステム設定を開きます。
「システムの詳細設定」→「環境変数」(又はAlt + N)をクリックします。
システム環境変数ZIG\_TOOL\_PATHを追加し、値をC:\\Zigに設定します。
更にPath変数を編集し、其の中にC:\\Zigを追加して下さい。

Neovimユーザーへ

プログラミングを始める前に、Neovimを使用している場合は、強制オートフォーマッタを無効化する事を強くお勧めします。
此れはあたしがZigから離れる原因にもなった要素です。
非常に長い間存在しているバグであり、ケレイさんは修正する意思がない様です。

$ less ~/.config/nvim/init.lua | grep "zig"
vim.g.zig_fmt_autosave = 0
vim.g.zig_fmt_parse_errors = 0

VS Codeユーザーへ

VS Code又はVS Codiumを使用している場合は、CTRL + ,を押して設定を開き、「zig」と検索し、Zig: Formatting ProviderOffに設定して下さい。

準備

> zig init
info: created build.zig
info: created build.zig.zon
info: created src\main.zig
info: created src\root.zig
info: see `zig build --help` for a menu of options
> del .\src\root.zig
> zig fetch --save git+https://github.com/zig-gamedev/zglfw.git
info: resolved to commit 51003c105d23db378bb59ce415a387b22f1b0892

build.zigの内容:

const std = @import("std");

pub fn build(b: *std.Build) void {
  const target = b.standardTargetOptions(.{});
  const optimize = b.standardOptimizeOption(.{});

  const exe = b.addExecutable(.{
    .name = "OpenGLレンダー",
    .root_module = b.createModule(.{
      .root_source_file = b.path("src/main.zig"),
      .target = target,
      .optimize = optimize,
    }),
  });

  const zglfw = b.dependency("zglfw", .{
    .target = target,
    .optimize = optimize,
  });
  exe.root_module.addImport("zglfw", zglfw.module("root"));
  exe.root_module.linkLibrary(zglfw.artifact("glfw"));
  exe.root_module.addIncludePath(b.path("./include"));
  exe.root_module.addCSourceFile(.{
    .file = b.path("src/glad.c" ),
  });

  b.installArtifact(exe);
}

ウインドウの作成

const std = @import("std");
const glfw = @import("zglfw");

pub fn main() !void {
  try glfw.init();
  defer glfw.terminate();

  const window = try glfw.Window.create(800, 600,
    "OpenGLレンダー", null, null
  );
  defer window.destroy();

  while (!window.shouldClose() and window.getKey(.q) != .press) {
    window.swapBuffers();

    glfw.pollEvents();
  }
}
> zig build
> dir .\zig-out\bin\


    ディレクトリ: C:\Users\suwako\dev\stub\opengl-render-zig\zig-out\bin


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2026/05/02      0:42        3102208 OpenGLレンダー.exe                                                                                                                                                       

-a----        2026/05/02      0:42        3796992 OpenGLレンダー.pdb                                                                                                                                                       


> .\zig-out\bin\OpenGLレンダー.exe

ウィンドウ#width: 100%; display: block; max-width: 592px; margin: auto;

ウィンドウの表示は正常に動作しました。
コードは非常にシンプルですが、コンパイルにはクッソに長い時間がかかります。

次に静的リンクのテストです。

$ cd /mnt/c/Users/suwako/dev/stub/opengl-render-zig
$ ls -thal zig-out/bin/OpenGLレンダー.exe
-rwxrwxrwx 1 suwako suwako 3.0M  5月  2 00:42 zig-out/bin/OpenGLレンダー.exe
$ file zig-out/bin/OpenGLレンダー.exe
zig-out/bin/OpenGLレンダー.exe: PE32+ executable for MS Windows 6.00 (console), x86-64, 7 sections
$ ldd zig-out/bin/OpenGLレンダー.exe
        動的実行ファイルではありません

デフォルトで静的リンクされている様です。
素晴らしいですね。
但し、過去の経験から、Zigはコードが純粋にZigだけで書かれている場合に限りデフォルトで静的リンクされる事を知っています。
C言語との相互運用(Zigの大きな強みの一つ)を使う場合、Unix系システムでは動的リンクになります。
ZGLFWは内部でCのGLFWライブラリを使用していますが、Windowsではゲーム機以外のOSと比べて静的リンクが比較的簡単です。

次はおなじみの四角形を描画します。
ZigのC言語相互運用の強力さと、OpenGLラッパーが古いかドキュメント不足になりがちである(オープンソースではよくある事)点を示す為、Zig内でCコード経由のOpenGLを使用します。

グラフィックスの描画

const std = @import("std");
const glfw = @import("zglfw");
const c = @cImport({
  @cInclude("glad/glad.h");
});

const VERTEX_SRC =
  \\#version 460 core
  \\
  \\layout (location = 0) in vec3 aPos;
  \\
  \\void main() {
  \\  gl_Position = vec4(aPos, 1.0);
  \\}
;

const FRAG_SRC =
  \\#version 460 core
  \\
  \\out vec4 FragColor;
  \\
  \\void main() {
  \\  FragColor = vec4(1.f, .5f, .2f, 1.f);
  \\}
;

fn framebuffer_size_callback(_: *glfw.Window, width: c_int, height: c_int) callconv(.c) void {
  c.glViewport(0, 0, width, height);
}

pub fn main() !void {
  try glfw.init();
  defer glfw.terminate();

  glfw.windowHint(.client_api, .opengl_api);
  glfw.windowHint(.context_version_major, 4);
  glfw.windowHint(.context_version_minor, 6);
  glfw.windowHint(.opengl_profile, .opengl_core_profile);

  const window = try glfw.Window.create(800, 600,
    "OpenGLレンダー", null, null
  );
  defer window.destroy();

  glfw.makeContextCurrent(window);
  _ = glfw.setFramebufferSizeCallback(window, framebuffer_size_callback);
  _ = c.gladLoadGLLoader(@ptrCast(&glfw.getProcAddress));

  const vertexShader = c.glCreateShader(c.GL_VERTEX_SHADER);
  c.glShaderSource(vertexShader, 1, @ptrCast(&VERTEX_SRC), null);
  c.glCompileShader(vertexShader);
  defer c.glDeleteShader(vertexShader);

  var success: c_int = 0;
  c.glGetShaderiv(vertexShader, c.GL_COMPILE_STATUS, &success);

  const fragShader = c.glCreateShader(c.GL_FRAGMENT_SHADER);
  c.glShaderSource(fragShader, 1, @ptrCast(&FRAG_SRC), null);
  c.glCompileShader(fragShader);
  defer c.glDeleteShader(fragShader);

  success = 0;
  c.glGetShaderiv(fragShader, c.GL_COMPILE_STATUS, &success);

  const shaderProgram = c.glCreateProgram();
  c.glAttachShader(shaderProgram, vertexShader);
  c.glAttachShader(shaderProgram, fragShader);
  c.glLinkProgram(shaderProgram);

  c.glGetProgramiv(shaderProgram, c.GL_LINK_STATUS, &success);

  const vertices = [_]f32{
     0.5,  0.5, 0.0,
     0.5, -0.5, 0.0,
    -0.5, -0.5, 0.0,
    -0.5,  0.5, 0.0,
  };

  const indices = [_]u32{
    0, 1, 3,
    1, 2, 3,
  };

  var VBO: u32 = 0;
  var VAO: u32 = 0;
  var EBO: u32 = 0;

  c.glGenVertexArrays(1, &VAO);
  defer c.glDeleteVertexArrays(1, &VAO);
  c.glGenBuffers(1, &VBO);
  defer c.glDeleteBuffers(1, &VBO);
  c.glGenBuffers(1, &EBO);
  defer c.glDeleteBuffers(1, &EBO);
  c.glBindVertexArray(VAO);

  c.glBindBuffer(c.GL_ARRAY_BUFFER, VBO);
  c.glBufferData(c.GL_ARRAY_BUFFER, vertices.len * @sizeOf(f32), &vertices, c.GL_STATIC_DRAW);

  c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, EBO);
  c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, indices.len * @sizeOf(u32), &indices, c.GL_STATIC_DRAW);

  c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 3 * @sizeOf(f32), null);
  c.glEnableVertexAttribArray(0);

  c.glBindBuffer(c.GL_ARRAY_BUFFER, 0);
  c.glBindVertexArray(0);

  while (!window.shouldClose() and window.getKey(.q) != .press) {
    c.glClearColor(0.6, 0.1, 0.6, 1.0);
    c.glClear(c.GL_COLOR_BUFFER_BIT);
    c.glUseProgram(shaderProgram);
    c.glBindVertexArray(VAO);
    c.glDrawElements(c.GL_TRIANGLES, 6, c.GL_UNSIGNED_INT, null);

    window.swapBuffers();
    glfw.pollEvents();
  }
}

ウィンドウ#width: 100%; display: block; max-width: 592px; margin: auto;

前回のOdinやRustのレビューと非常によく似ています。

総評

良い点:

  • インストールが簡単
  • 優れたCコンパイラ
  • 数十年分のCライブラリをそのまま利用可能
  • ビルドシステムとテストスイートが標準搭載
  • BSDの一級サポート
  • コンパイル時処理が豊富でランタイム計算を削減できる
  • デフォルトで静的リンク

悪い点:

  • 強制オートフォーマッタ(他言語と違いオプトアウト方式)が非常に煩わしい
  • プロジェクト初期化にバグがある
  • コンパイラが厳しすぎる場合がある
  • エコシステム志向が強すぎる
  • ドキュメントがまだ不十分
  • Cライブラリ使用時は静的リンク不可(Unix系のみ、Windowsは除く)

もう一つの欠点として、バージョンごとに仕様が大きく変わる点があります。
但し此れはZigが未だ完成品ではなく、1.0以降に安定したABIを保証するとしている為です。
現時点では欠点ですが、将来的には解消されるでしょう。

Zigは0.11.0から大きく進化しましたが、最も厄介なバグ(強制フォーマッタ等)は未だに修正されておらず、恐らく1.0でも残るでしょう。
Zigは、組み込みプログラミング初心者で、C言語の無制限なメモリアクセスやRustの学習コストに不安を感じる人にとって、有力な選択肢になり得ます。
此のニッチに最も適している言語だと思います。

ゲーム開発においては、PCゲームでは今後Zigの利用が増える可能性がありますが、ゲーム機では引き続きC++が主流であり続けるでしょう。

又、Zigコミュニティは全体的に「ゼロから自分で書く」志向が強いのも特徴です。
Rustのレビューでは、OpenGLやGLFWを導入するだけで多くの依存関係が追加される点に不満を述べました。
然しzig-pkgディレクトリを確認すると、依存関係はわずか2つだけです:此のレンダラーが依存するzglfwと、GLFWが依存するsystem_sdkのみです。

次回はmacOSでObjective-CとMetal API、MSL(メタル・シェーディング言語)を扱う予定です。
其の後はiOSでSwiftを扱います。
此れらは完全に未経験の分野なので、全てをゼロから学びながら解説していきます。
本来は次にGoを扱う予定でしたが、MacBook Neoを購入した事と、此の分野について書いている人が殆どいない為、Objective-CとMetal APIを試す事にしました。

本記事はもっと早く公開する予定でしたが、コンパイラとの格闘に時間を取られ、1週間以上遅れてしまいました。
又、各言語レビューで作成したレンダラーは、Microsoft Github及びCodebergにコミットし始めています。

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?