Zig言語で自己依存モジュールを作成する

## TL;DR

以下の`Issue`にやり方書かれてるから読むよろし。

https://github.com/ziglang/zig/issues/14708

## はじめに

まず自己依存モジュールとは、モジュールの内側から自身が属するモジュールをインポートすることを指している。

具体的には、`x1.zig`と`x2.zig`がモジュール`x`に属している時

```zig
// x1.zig

const x = @import("x");
// ....
```

```zig
// x2.zig

const x = @import("x");
// ....
```

のように記述すること。

`std`モジュール内で割とよく見かけると思う。

小さなモジュールであれば、各ソースユニットを相対パスでインポートすればよいが、規模が大きくなると自己依存モジュールが効果を発揮する。

ところでこの自己依存モジュール、バージョン0.10.x系では、何も考えなくても自己依存モジュールを作ることができた（らしい）。
しかし、バージョン0.11.x以降[^1]、ちょっと意識して実装する必要がある模様。

方法については、以下の`Issue`に記載されている。

https://github.com/ziglang/zig/issues/14708

この記事は、`Issue`の内容を頑張って噛み砕いてみた記録。

[^1]:正確には、 https://github.com/ziglang/zig/issues/14307 でモジュール管理周りが再構成されたあたり以降

## 環境

* zig 0.11系のmaster

`Zig`言語がまだバージョン1.0に到達していないということもあり、将来的にちゃぶ台返されるかもしれないことに注意。

## 準備

ここでは説明のために、自身のパッケージの中にモジュールを作って直接利用する構成とする。[^2]

[^2]:パッケージを分け、別パッケージから利用するためには新しくなったパッケージ管理に従う必要があり説明するのが厄介なため

```
├── build.zig
└── src
    ├── main.zig
    ├── mod_a
    │   ├── a.zig
    │   ├── bar.zig
    │   └── foo.zig
    └── mod_a.zig
```

それぞれの内容は、以下の通り。

* mod_a/a.zig

```zig
const mod_a = @import("mod_a");

pub fn a1() void {
    mod_a.foo("foo_1");
}

pub fn a2() void {
    mod_a.bar();
}
```

* mod_a/bar.zig

```zig
const std = @import("std");

pub fn bar() void {
    std.debug.print("bar called\n", .{});
}
```

* mod_a/foo.zig

```zig
const std = @import("std");

pub fn foo(name: []const u8) void {
    std.debug.print("{s} called\n", .{ name });
}
```

* mod_a.zig

```zig
pub const foo = @import("./mod_a/foo.zig").foo;
pub const bar = @import("./mod_a/bar.zig").bar;

const a = @import("./mod_a/a.zig");
pub const a1 = a.a1;
pub const a2 = a.a2;
```

* main.zig

```zig
const mod_a = @import("mod_a");

pub fn main() void {
    mod_a.a1();
    mod_a.a2();
}
```

## 実装方法

`build.zig`でモジュールが利用できるよう宣言する

1\. `build`関数内で、`std.Build.createModule`関数を使用してモジュールを定義する。

```zig
// createModule に渡す引数の型はCreateModuleOptions
// CreateModuleOptions 型は以下のような定義
//
//   pub const CreateModuleOptions = struct {
//       source_file: FileSource,
//       dependencies: []const ModuleDependency = &.{},
//   };
//
// FileSource 型は以下のようなunion型
// 通常はpathフィールドを指定することになるだろう
//
//   pub const FileSource = union(enum) {
//    /// A plain file path, relative to build root or absolute.
//    path: []const u8,
//
//    /// A file that is generated by an interface. Those files usually are
//    /// not available until built by a build step.
//    generated: *const GeneratedFile,
//    ....
const mod_a = b.createModule(.{ .source_file = .{ .path = "./src/mod_a.zig" } });
```

2\. 作成したモジュール自身を自身の依存リストに加える。

```zig
// dependencies の型は、std.StringArrayHashMap(*Module)
// キーに自身のモジュール内でインポートの際に指定する名前
// 値に作成したモジュールを渡す
try mod_a.dependencies.put("mod_a", mod_a);
```

3\. `CompileStep.addModule`メソッドを使用して、実行モジュールに作成したモジュールを追加する

```zig
// 第一引数は、モジュール内でインポートの際に指定する名前
// 第二引数は、作成したモジュール
exe.addModule("mod_a", mod_a);
```

4\. `try`を使うため、`build`関数の戻り値を`!void`にする。

全体をまとめると、`build.zig`は以下のようになる。

```zig
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) !void {
    // Standard target options allows the person running `zig build` to choose
    // what target to build for. Here we do not override the defaults, which
    // means any target is allowed, and the default is native. Other options
    // for restricting supported target set are available.
    const target = b.standardTargetOptions(.{});

    // Standard optimization options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
    // set a preferred release mode, allowing the user to decide how to optimize.
    const optimize = b.standardOptimizeOption(.{});

    const mod_a = b.createModule(.{ .source_file = .{ .path = "./src/mod_a.zig" } });
    try mod_a.dependencies.put("mod_a", mod_a);

    const exe = b.addExecutable(.{
        .name = "self_depend_module_app",
        // In this case the main source file is merely a path, however, in more
        // complicated build scripts, this could be a generated file.
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    exe.addModule("mod_a", mod_a);

    // This declares intent for the executable to be installed into the
    // standard location when the user invokes the "install" step (the default
    // step when running `zig build`).
    exe.install();

    // This *creates* a RunStep in the build graph, to be executed when another
    // step is evaluated that depends on it. The next line below will establish
    // such a dependency.
    const run_cmd = exe.run();

    // By making the run step depend on the install step, it will be run from the
    // installation directory rather than directly from within the cache directory.
    // This is not necessary, however, if the application depends on other installed
    // files, this ensures they will be present and in the expected location.
    run_cmd.step.dependOn(b.getInstallStep());

    // This allows the user to pass arguments to the application in the build
    // command itself, like this: `zig build run -- arg1 arg2 etc`
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    // テストの構成は同じなので省略
```

### 2023-6-3 追記

時期までは詳細に追いかけていないですが、少なくとも `0.11.0-dev.2969+855493bb8` のバージョンで`std.Build`のAPIが変更されています。
具体的には、`exe.install()`の箇所。

`0.11.0-dev.2969+855493bb8`のバージョンでは、以下のように変更する必要があります。[^3]

[^3]: 元の`build.zig`のバックアップをとっておき、`zig init-exe`で`build.zig`を再作成したのち、バックアップから必要な箇所を置き換える方法でもOK

```zig
b.installArtifact(exe);
```

## 注意点

以下のように、モジュール内のユニットに直接触るとコンパイルエラー(`imported from module root`)になるので注意。

```zig
const mod_a = @import("mod_a");

// 直接モジュール内のユニットに触ってる
const bar = @import("./mod_a/bar.zig").bar;

pub fn main() !void {
    mod_a.a1();
    mod_a.a2();

    bar();
}
```

```
Build Summary: 0/3 steps succeeded; 1 failed (disable with -fno-summary)
install transitive failure
└─ install self_depend_module transitive failure
   └─ zig build-exe self_depend_module Debug native 1 errors
src/mod_a/bar.zig:1:1: error: file exists in multiple modules
src/main.zig:3:21: note: imported from module root
const bar = @import("./mod_a/bar.zig").bar;
                    ^~~~~~~~~~~~~~~~~
src/mod_a.zig:2:25: note: imported from module root.mod_a
pub const bar = @import("./mod_a/bar.zig").bar;
                        ^~~~~~~~~~~~~~~~~
```