11 — モジュールとパッケージ

前: Option とレールウェイ

English version: 11_modules_packages.vibe.md

1ファイルが1モジュールです。export と書かない限り他のファイルからは何も 見えず、import と書かない限り自分のファイルには何も入ってきません。 必要なことのほとんどはこの2語です。

2つのファイル

実物です。support/mathx.vibe に export された関数が あります:

export fn triple(x: Int) -> Int {
  x * 3
}

この章は相対パスで、欲しい名前を指定して import します:

import ./support/mathx.vibe {
  triple
}

fn main with Console {
  println("triple(14) = \{triple(14)}")
}
triple(14) = 42

import 行のバリエーション:

2つのモジュールが良い名前について意見を異にするときのためのものです。

index.vibe に解決されます。

名指しします。値は裸のままです。type / struct / enum / effect / trait では修飾子は厳密で — Color が enum のときに struct Color を頼むと 拒否されます — 非値 import の裸綴りは互換のためだけに今は受理されて います。

相対 import はエントリファイル自身のディレクトリより上に出られます — 入れ子のエントリからの import ../../../helper.vibe は解決します。境界に なるのはエントリのディレクトリではなく、ホストがコンパイラに見せた範囲 (preopen したディレクトリ) です。その外のパスは、../ を何段重ねても 見つかりません。

パッケージをまたぐのはさらに別の境界です。index.vpkg を持つディレクトリ はその契約を公開するので、export していない名前は黙って import されるので はなく検査時に拒否されます。

名前で指すパッケージ

@ で始まる import はパスではなくパッケージを指します:

import @vibe/core {
  hex_encode, sha1
}

fn main with Console {
  println("length(sha1(\"vibe\")) = \{String::length(sha1("vibe"))}")
  println("hex_encode(\"hi\") = \{hex_encode("hi")}")
}
length(sha1("vibe")) = 40
hex_encode("hi") = 6869

名前は3箇所を順に探します: プロジェクトの pin 済みストア、ワークスペース 自身の lib/、インストール済みの標準ライブラリ。最初に見つかったものが 勝つので、作業中はローカルのコピーがインストール済みのものを覆い隠します。

契約ファイル

パッケージは、たまたま export されているものを外に出すわけではありません。 公開 API を index.vpkg — 本体のない宣言の契約 — として宣言し、 コンパイラが実装をそれと突き合わせます。食い違えばコンパイルエラーであり、 利用者にとっての驚きにはなりません。

name = @you/counter
version = 1.0.0
description =
  #|A tiny counter contract
deps = {}

generated_hash =

type Counter
fn add(x: Int, y: Int) -> Int

定義のない type Counter は、利用者に名前だけを与えて表現は与えない、 という意味です。後から変えられます。宣言の上のヘッダは vibe の構文では なくパッケージのメタデータで、 docs/adding-modules.md が参照先です。

契約は利用者に向けた境界です。他のパッケージから何に手が届くかを 決めるものであって、パッケージの内側では邪魔をしません。最も近い index.vpkg が同じ2ファイルは、export して直接 import すれば ヘルパーを共有できます。契約に書く必要はありません:

pkg/index.vpkg     fn package_value() -> Int          <- 公開するのはこれだけ
pkg/impl.vibe      import ./_helper.vibe { private_value }
pkg/_helper.vibe   export fn private_value() -> Int { 41 }

これはコンパイルが通ります。内部ヘルパーは内部のままでいられます — 契約に書くことは、兄弟ファイルから使えるようにする操作ではなく、 公開 API にする操作です。

再現可能なビルド

他人のパッケージに依存するとき、検証されるのはバージョン番号ではなく 内容ハッシュです。pin は自分のパッケージの index.vpkg の先頭、 宣言部より上のメタデータヘッダに書く require 行です:

require @vibe/core 0.2.0 = #pkg:sha1:<40hex>

ビルドのたびにそのハッシュをオフラインで再検査するので、ビルドとビルドの 間、レジストリもネットワークも信頼する必要がありません。値は vibe hash が計算します。VIBE_REQUIRE_PINS=1 を設定すると pin の無い依存はエラーに なります。リリースビルドはそうあるべきです。

(第1章の vibe.deps は別の、より粗い仕組みです: vibe add / vibe fetch がリポジトリごと deps/ に vendor するための <name> <url> 行を並べます。 require pin はレジストリのレーンで、パッケージ単位・内容アドレスで、 コンパイラ自身が検査します。)

公開する

誰かに渡す準備ができたら:

vibe pkg publish lib/@you/pkg     # バージョン検査の後、ログに追記
vibe pkg install @you/pkg@1.0.0   # 取得してログと照合
vibe pkg add github:owner/repo/dir@ref
vibe pkg yank @you/pkg@1.0.0      # バージョンを取り下げる
vibe pkg update @you/pkg          # 最新へ移動し、契約の差分を表示

publish と yank は透明性ログに追記され、install はその証明を検証します — なので、後からバージョンを差し替えられることはありません。設計は docs/registry-design.md にあります。

次: テストを書く