モジュールシステム仕様
本文書は YaoXiang プログラミング言語のモジュールシステム仕様を定義する。モジュール定義、インポート・エクスポート、スコープを含む。
第一章:モジュール定義
1.1 モジュールの基礎
モジュールはファイルを境界として使用する。各 .yx ファイルが一つのモジュールとなる。
// ファイル名がそのままモジュール名
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }1.2 モジュールの命名規則
- モジュール名はファイル名によって決定される
- ファイル拡張子
.yxはモジュール名に含まれない - モジュール名は PascalCase で命名する
第二章:モジュールのインポート
2.1 インポート構文
Import ::= 'use' ModuleRef ImportSpec?
ImportSpec ::= ('{' ImportItems '}') ('as' AliasList)?
| 'as' AliasList
ImportItems ::= Identifier (',' Identifier)* ','?
AliasList ::= Identifier (',' Identifier)*2.2 インポート方法
| 構文 | 説明 | 例 |
|---|---|---|
use path; | モジュールをインポートし、最後の部分でアクセス | use std.io; → io.print |
use path.{a, b}; | 指定した項目をインポート | use std.io.{print}; → print |
use path as alias; | インポートして別名にする | use std.io as io; → io.print |
use path.{i1, i2} as a, b; | 指定した項目をインポートし別名にする | use std.io.{print, read} as p, r; → p, r |
2.3 インポート例
// モジュール全体をインポート
use std.io
io.print("Hello")
// 指定した項目をインポート
use std.io.{print, read}
print("Hello")
// インポートして別名にする
use std.io as io_module
io_module.print("Hello")
// 指定した項目をインポートして別名にする
use std.io.{print, read} as p, r
p("Hello")第三章:モジュールのエクスポート
3.1 pub キーワード
pub キーワードでエクスポート項目を宣言する:
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }
// 非公開項目(エクスポートしない)
internal_value: Int = 423.2 エクスポート規則
- デフォルトでは全ての項目が非公開である
pubで宣言された項目は他のモジュールからアクセス可能- 非公開項目は現在のモジュール内のみでアクセス可能
3.3 pub 自動バインディング
pub で宣言された関数は、コンパイラが同ファイルで定義された型に自動的にバインドする:
// pub で宣言すると、コンパイラが自動的にバインド
pub distance: (p1: Point, p2: Point) -> Float = {
dx = p1.x - p2.x
dy = p1.y - p2.y
(dx * dx + dy * dy).sqrt()
}
// コンパイラの自動推論:
// 1. Point は現在のファイルで定義されている
// 2. 関数の引数に Point が含まれている
// 3. Point.distance = distance[0] を実行
// 呼び出し
d = distance(p1, p2) // 関数形式
d2 = p1.distance(p2) // OOP シンタックスシュガー第四章:スコープ
4.1 モジュールスコープ
各モジュールは独自のスコープを持ち、モジュール内の項目はデフォルトでは外部から見えない。
4.2 ネストされたスコープ
// ブロックスコープ
{
x = 10
// x はこのスコープ内で見える
}
// x はこのスコープ外では見えない
// 関数スコープ
add: (a: Int, b: Int) -> Int = {
result = a + b
return result
}
// result は関数の外では見えない4.3 変数宣言とシャドーイング
YaoXiang には let キーワードがない。x = value は宣言なのか代入なのか?一つの原則に従う:
代入優先。 宣言は一度だけ、代入は何度も行われる。高頻度の操作に最短経路を取らせる。
x = value:
スコープチェーンを外側にたどって x を検索
→ mut x が見つかった :代入、OK(&mut トークン経由)
→ x が見つかった(不変、生存中):E2010 再代入不可
→ 見つからない :現在のスコープで新規宣言(唯一の宣言経路)
mut x = value:
→ 現在のスコープに x が既に存在する :E2002 重複定義
→ 外側スコープに x が存在する :E2013 シャドーイング禁止(明示的な新規宣言は外側と同名不可)
→ 競合なし :新規の可変宣言- 同一スコープ:如何なる名前も一度しか宣言できない(E2002)
- 内側に
mutがない:外側を優先検索し、代入またはエラー - 内側に
mutがある:明示的な新規宣言、外側と同名は禁止(E2013)
ブロックは真のスコープ:「スコープチェーンをたどる」の前提は、各
{}ブロックが実際に一層を確立することである——内側で新規宣言された名前は外側に漏れない(§2.15 参照)。
同一スコープ
x = 10
x = 20 // E2010:'x' は不変、再代入不可
mut y = 10
y = 20 // OK:同一バインディング、再代入
mut y = 30 // E2002:'y' は既にこのスコープで定義済み(明示的な新規宣言が衝突)
z = 10
mut z = 20 // E2002:'z' は既にこのスコープで定義済み(mut は既存宣言を上書き不可)x = 20 が報告するのは E2010(再代入不可)であり E2002(重複定義)ではない点に注意: mut を伴わない x = value の意味は代入(スコープチェーンを辿る検索)であり、「x を再宣言する」ではない。明示的な新規宣言となるのは mut x = value のみで、衝突時に E2002 が報告される。
Move 後の再バインディング
不変変数が所有権を保持している場合、その値が move(消費)されると、元バインディングは moved 状態になる——名前は依然としてスコープのスロットを占有するが、値はもはやアクセスできない。
moved は「宣言判定」の入力ではない。 その名前を再度取得する手段は明示的な再宣言である:
// パイプライン式データフロー:各ステップで旧値を消費し新値を生成
mut data = fetch() // 明示的な可変宣言
mut data = transform(data) // E2002:同一スコープに既に data がある「moved → 再宣言可能」を採用しない理由(2026-09-19 改訂):
move はパス依存のデータフロー属性である——
if c { move p }の後、pは一つのパスでは move 済み、別のパスでは未 move という「move 済みの可能性」であり、布尔的な真偽ではない。バインディングレベルのフラグ構造では分岐合流を表現できず、それを「名前再宣言可能」のスイッチとして使うと誤った診断を与える(単一分岐の move が「move 済み」として扱われる)。真の move 解析は
layers/ownership.rsにある:関数本体の CFG を構築し、Alive < Moved < Droppedの束でデータフローを行い、分岐合流では max を取る(保守的)、読み取りチェックポイントで E2014/E2018 を報告する。これは「ここで読めるか」に答えるものであり、「この名前を再宣言できるか」は §4.3 の代入優先規則が独立して答える。二つの責務は異なり、結合すべきではない。これが旧文の「moved 済み分岐」が決して到達不能であった理由である:それは決して書き込まれないフラグに依存していた。
再バインディングは mut による明示宣言で実現する:
// 等価な明示的書き方
mut data1 = fetch()
data2 = transform(data1) // data1 は move され、再使用不可
mut data3 = filter(data2)意味の分離:
| 操作 | 意味 | 機構 | 構文 |
|---|---|---|---|
| 再バインディング | 旧値が消滅し、新値が誕生 | move + 新規宣言 | mut x = f(x)(新名または mut) |
| インプレース変更 | 同一メモリ位置の値変化 | mut 代入 | mut x = v; x = w |
制約:
- 所有権を保持する値のみが move される。参照(
&T、&mut T)はコピーされ move されない - Move チェックはコンパイル時に行われる(CFG + データフロー)、moved 状態の変数を如何なる式で読み取っても E2014 が報告される
- IDE は moved 変数に灰色ヒントを表示し、その名前が未初期化状態であることを示せる
- 宣言には初期値が必要:
LetStmt ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr(§3.2)——x: Intのような純粋な注釈宣言は文法違反となり、E0012 が報告される
// move 後の読み取り → エラー
data = fetch()
result = process(data) // data は move される
print(data) // E2014:'data' は move 済み、再使用不可
// 参照は move をトリガーしない
ref_data = &value
copy1 = ref_data // 参照をコピー、ref_data は依然使用可能
copy2 = ref_data // OK
// スコープをまたぐ:moved 状態の貫通
data = fetch()
{
data = transform(data) // 外側の data を move → 再バインディング(内側の新規宣言)
print(data) // OK:内側の data を使用
}
print(data) // E2014:外側の data は move 済みスコープをまたぐ場合
// 外側不変、内側代入 → 不変変数は再代入不可
x = 10
{
x = 20 // E2010:'x' は不変、再代入不可
}
{
mut x = 20 // E2013:既存変数 'x' をシャドーイング不可(明示的な新規バインディング宣言)
}
// 外側 mut、内側代入 → 同一バインディングの変更
mut y = 10
{
y = 20 // OK:同一バインディング、&mut トークン経由で変更
}
print(y) // 20
// 外側 mut、内側は同名を宣言不可
mut z = 10
{
z = 30 // OK:同一バインディング
}
{
mut z = 30 // E2013:既存変数 'z' をシャドーイング不可
}
// 多層ネスト:mut は全層を貫通する
mut a = 0
{
{
a = 10 // OK
}
}
print(a) // 10
// 不変も全層を貫通し、再代入不可
b = 0
{
{
b = 10 // E2010:'b' は不変、再代入不可
}
}for ループ
// ループ変数は各イテレーションで新バインディングとなり、変更ではない
for i in 1..5 {
print(i) // OK:各イテレーションで新値をバインド
i = 10 // E2010:不変ループ変数は再代入不可
}
for mut i in 1..5 {
i = 10 // OK:可変ループ変数
}
// ループ変数は外側をシャドーイング不可
i = 0
for i in 1..5 { // E2013:既存変数 'i' をシャドーイング不可
}
// 外層の mut アキュムレータはループ体内で変更可能
mut sum = 0
for i in 1..5 {
sum = sum + i // OK:同一バインディング、&mut トークン経由で変更
}
print(sum) // 15
// 外層の不変変数はループ体内で変更不可
sum2 = 0
for i in 1..5 {
sum2 = sum2 + i // E2010:'sum2' は不変、再代入不可
}関連エラーコード
| エラーコード | メッセージ | トリガー场景 |
|---|---|---|
| E2002 | '{name}' is already defined in this scope | 同一スコープでの重複宣言(mut であるか否かを問わない) |
| E2010 | Cannot assign to immutable variable '{name}' | 内側に mut がない代入時、外側変数が不変かつ未 moved |
| E2013 | Cannot shadow existing variable '{name}' | 内側の明示的宣言(mut x または x: Type)が外側と同名 |
| E2014 | '{name}' has been moved and cannot be used | moved 済み変数の読み取り |
第五章:モジュール構成
5.1 ディレクトリ構造
src/
├── main.yx // メインモジュール
├── math/
│ ├── index.yx // 数学モジュールのエントリ
│ ├── vector.yx // ベクトルモジュール
│ └── matrix.yx // 行列モジュール
└── utils/
├── index.yx // ユーティリティモジュールのエントリ
└── string.yx // 文字列ユーティリティ5.2 モジュールエントリ
ディレクトリ内の index.yx ファイルがモジュールのエントリとなる:
// math/index.yx
use math.vector
use math.matrix
pub Vector = vector.Vector
pub Matrix = matrix.Matrix5.3 相対インポート
// math/vector.yx 内
use math.matrix // 絶対インポート
use .matrix // 相対インポート(同ディレクトリ)付録:モジュール構文クイックリファレンス
A.1 モジュール=ファイル
// ファイル名.yx がそのままモジュール名
Import ::= 'use' ModuleRefA.2 インポート・エクスポート
// インポート
use std.io
use std.io.{print, read}
use std.io as io
// エクスポート
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }