Chapter 01

Nixの形而上学:
不変性とMerkle DAGによる構成管理

なぜ従来のパッケージ管理は「状態」に敗北するのか。
計算機科学的アプローチによる、OSの完全な再現性の証明。

1. なぜNixOSなのか:副作用と状態の排除

1990年代に設計された FHS (Filesystem Hierarchy Standard) は、当時の限られたストレージ資源を節約するために「共有」を至上命題としていました。しかし、この共有こそが現代の複雑化したソフトウェア依存関係における「原罪」となっています。

FHSが提供する /usr/lib/etc は、システム全体で共有される 「グローバルな可変状態 (Global Mutable State)」 です。あるパッケージをインストールするために共有ライブラリをアップデートする行為は、他のすべてのパッケージの動作環境を、彼らの預かり知らぬところで書き換える「副作用」を伴います。

ABI不整合という「静かな死」

依存関係の問題の本質は、ライブラリのバージョン番号の衝突だけではありません。真に深刻なのは ABI (Application Binary Interface) の不整合です。

シンボル解決の裏側に潜む罠

例えば、共有ライブラリ libssl.so.3 が更新され、内部で使用している構造体のメモリ配置(フィールドの順序やオフセット)が変更されたとします。ダイナミックリンカはファイル名が一致すれば解決に成功したとみなしますが、古いレイアウトを期待するバイナリがこの新しいライブラリをロードした瞬間、ポインタが不正なアドレスを指し、実行時に Segmentation Fault を引き起こします。

命令的パッケージマネージャ(APT/RPM)は、この物理的な整合性を保証できません。「最新のパッチは常に後方互換性がある」という楽観的な仮定に依存しているため、アップデートは常に破壊のリスクを伴うギャンブルとなります。

NixOSはこの「共有の呪い」を解くために、グローバルな名前空間を放棄します。すべてのパッケージは、その「入力」から導き出された一意のハッシュ値を持つ独立したディレクトリに隔離されます。パッケージの導入は、既存環境の「上書き」ではなく、「不変オブジェクトの追加」 として処理されるため、副作用が論理的に発生し得ない構造となっています。

2. 宣言的モデル:Build = Function(Input)

Nixの設計哲学の根幹は、パッケージビルドを数学的な 「純粋関数 (Pure Function)」 と定義することにあります。純粋関数とは、「同じ入力に対して常に同じ出力を返し、外部の状態を一切変更しない」性質を持ちます。この「参照透過性」をOSの構築に持ち込むことで、完全な再現性を実現しています。

Build\_Output = f(Source, Dependencies, Toolchain, Build\_Script, Arch)

ビルドサンドボックス:論理性への物理的裏付け

「純粋であること」を数学적理想に留めないため、NixはLinuxカーネルの高度な機能である Namespaces(名前空間)cgroups を駆使した厳格なサンドボックス環境でビルドを強制します。

3. Nix Storeの構造とハッシュ参照

Nixのストアは Input-Addressed Storage(入力アドレス指定ストレージ) です。Gitのような「内容(ファイル自体)」のハッシュではなく、「そのパスを作るための設計図(レシピ)」 のハッシュに基づいてパスが決定されます。

Anatomy of a Store PathConcept
/nix/store/b6gvzjyb2jg7...-openssl-3.0.1/
           ^---------^   ^-----------^
            Hash (160bit)     Name

このハッシュ値(nix32エンコーディング)には、ソースコードのハッシュだけでなく、使用したコンパイラのバイナリ、渡されたコンパイルフラグ、さらには依存するすべてのライブラリのハッシュが含まれます。フラグを一つでも変更すれば、それは別のディレクトリとして保存され、既存の環境を破壊することなく共存します。

Merkle DAG:依存関係の「バタフライ・エフェクト」

Nixのストア全体は、一つの巨大な Merkle DAG (Directed Acyclic Graph) を形成しています。依存関係の下層にあるパッケージ(例: glibc)に変更があれば、そのハッシュ変化がグラフを遡り、依存するすべてのパッケージのパスを再計算・再構築させます。

nix32エンコーディングの知恵

パスに含まれる文字列は、SHA-256(256ビット)を160ビットに切り詰め、視認性が低く間違いやすい `e, o, u, t` を除外した独自の32進数でエンコードされています。これは、人間によるデバッグの容易さと、ファイルシステム上の文字数制限を両立させるための、Nix独自の巧妙な設計です。

4. Derivation:純粋関数からビルド命令へ

私たちが .nix ファイルに記述するコードは、直接バイナリを作るものではありません。Nix言語の評価結果は、ビルドの「静的な青写真」である Derivation (.drv) ファイルです。

これはコンパイラにおける中間表現(IR)に相当し、ホストシステムの状態に依存しない「実行すべきコマンドのリスト」がシリアライズされています。Nixはこの設計図を読み取り、前述のサンドボックス内で具象化(Realisation)を行います。

ダイナミックリンクの再発明:RPATHパッチング

サンドボックス内で生成されたバイナリを、標準的なLinux環境で実行可能にするには「魔法」が必要です。通常のバイナリは /lib64/ld-linux.so.2/usr/lib を探しに行きますが、NixOSにはそのパスがありません。

クロージャ (Closure) の完成

Nixはビルドの最終段階(fixupPhase)において、バイナリのELFヘッダーにある RPATH (Run-time Search Path)Interpreter Path を書き換えます。バイナリがストア内の特定のライブラリを「絶対パス」で見に行くように焼き付けるのです。

これにより、Nix製のバイナリは環境変数 LD_LIBRARY_PATH に頼ることなく、常に「ビルド時と全く同じライブラリ」をロードします。この「自己完結した依存関係の集合」をクロージャと呼びます。これが、NixOSにおける圧倒的な堅牢性の物理的な正体です。