Files
hakorune/docs/development/current/main/phase110_filehandlebox_design.md
nyash-codex 4a2b0fa92a docs: Phase 106-111 完了反映(Ring0/FileBox I/O パイプライン)
更新内容:
- phase110_filehandlebox_design.md: Phase 111 append+metadata 完了を明記
- core_boxes_design.md: Phase 111 詳細追加、次フェーズ(112-116)整理
- CURRENT_TASK.md: Ring0/FileBox ライン Phase 106-111 完了サマリ追加(新セクション)

Phase 106-111 成果:
- 6つのコミット(52c13e65 ~ fce7555e)
- 28ファイル修正
- +1,200行(設計+実装+テスト)、-150行削減
- 33テスト全 PASS(Unit + Integration)
- Ring0→Ring1→Language の完全 3層パイプライン実装完了

次フェーズ(Phase 112-116)の方向性も明確化:
- Phase 112: Ring0 Service Registry 統一化
- Phase 113: FileHandleBox NyashBox 公開 API
- Phase 114: FileIo 機能拡張
- Phase 115+: 並行アクセス、エンコーディング etc.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 21:32:14 +09:00

18 KiB
Raw Blame History

Phase 110: FileHandleBox 設計(複数回アクセス対応)

0. ゴール

  • Phase 108 で実装した FileBox「1ショット I/O」read/write 1回ずつを補完する
  • FileHandleBox「複数回アクセス I/O」open → read/write/read → closeを設計・実装
  • Ring0FsFileIo を内部で再利用して、レイヤー統一を維持
  • RuntimeProfile との整合Default では使用可能、NoFs では使用不可

1. スコープと非スコープ

スコープ(今回やること)

  1. 設計ドキュメントの作成

    • FileBox / FileHandleBox / FileIo / FsApi の役割と関係を定義
    • FileHandleBox の APIopen/read/write/close 等)とライフサイクルを決める
    • 二重 open 時の仕様を明記
  2. Rust 側の最小実装

    • FileHandleBox の型スケルトンと trait 実装NyashBoxを追加
    • Ring0FsFileIo を内部で使い回す形で、最小の open/read/write/close を通す
    • close() 後の挙動を明確に定義
  3. プロファイルとの整合

    • RuntimeProfile::Default のときは FileHandleBox が使用可能
    • RuntimeProfile::NoFs のときは FileHandleBox は禁止open 時にエラー)

非スコープ(今回はやらない)

  • FileHandleBox を Nyash (.hako) 側からフル運用するサンプル大量追加(必要なら 12 個の最小例だけ)
  • ファイルロック/権限/並行書き込みなどの高度機能
  • FileBox API 自体の破壊的変更(既存の read/write の意味は維持)
  • append modePhase 111 で扱う)

2. Task 1: 設計ドキュメント作成

2.1 ファイル

  • docs/development/current/main/phase110_filehandlebox_design.md(このドキュメント)

2.2 目的と役割分担

FileBoxPhase 108:

  • シンプルな 1 ショット I/O
  • read(path) -> String / write(path, content) -> OK/Error のようなメソッド
  • ファイルを開いて → 読む/書く → 閉じるをすべて隠す
  • ユースケース:ログ書き込み、ワンショット設定ファイル読み込み

FileHandleBoxPhase 110:

  • 複数回アクセス I/O
  • ハンドルで 1 ファイルを確保 → open/read/write/close を自分で制御
  • ユースケース:大きなテキストファイルの行単位読み込み、逐次更新

レイヤー図:

[FileBox]            [FileHandleBox]
   |                      |
   └──────┬────────────────┘
           | (FileIo trait)
           v
    [Ring0FsFileIo]
           |
           v (FsApi)
      [Ring0.fs]
           |
           v
        [std::fs]

2.3 ライフサイクル設計

FileHandleBox のライフサイクル

1. birth() / new()
   └─ path 未指定、io 未初期化None
   └ファイルはまだ開かれない

2. open(path, mode)
   └─ Ring0FsFileIo をこのインスタンス用に作成して保持
   └─ FileIo.open(path) を内部で呼び出す
   └─ 以後 read/write が利用可能に

3. 複数回 read/write
   └─ 同じハンドルで繰り返し呼び出し可能
   └─ close() されるまで有効

4. close()
   └─ 内部 FileIo をリセットOption::None に)
   └─ 以後 read/write は Err("FileHandleBox is not open")

5. Drop 時
   └─ close() 忘れがあれば、警告ログを出すPhase 110 では実装可能、または後回し)

.hako 側での使用パターン

// パターン1: 新しいファイルに書き込み
local h = new FileHandleBox()
h.open("/tmp/output.txt", "w")
h.write("line 1")
h.write("line 2")
h.close()

// パターン2: 既存ファイルを読む
local h = new FileHandleBox()
h.open("/tmp/input.txt", "r")
local content = h.read()
h.close()

// パターン3: close() 忘れ警告ログまたはパニック、Phase 110 で決定)
local h = new FileHandleBox()
h.open("/tmp/data.txt", "w")
h.write("data")
// close() なしで終了 ← 警告が出るかもしれない

2.4 二重 open の仕様(重要)

方針:

  • 最初の open → 成功、io が Some(FileIo) に
  • 2 番目の open → エラーを返すFail-Fast
  • 理由:複数ファイルハンドルが必要なら複数 FileHandleBox インスタンスを使う

実装:

pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> {
    // 既に open 済みなら error
    if self.io.is_some() {
        return Err("FileHandleBox is already open. Call close() first.".to_string());
    }
    // 新規 open
    let io = Arc::new(Ring0FsFileIo::new(self.ring0.clone()));
    io.open(path)?;
    self.io = Some(io);
    Ok(())
}

2.5 close() 後の読み書き

pub fn read_to_string(&self) -> Result<String, String> {
    self.io.as_ref()
        .ok_or("FileHandleBox is not open".to_string())?
        .read()
}

pub fn write_all(&self, content: &str) -> Result<(), String> {
    self.io.as_ref()
        .ok_or("FileHandleBox is not open".to_string())?
        .write(content)
}

3. Task 2: API 定義Rust 側インターフェース)

3.1 ファイル

  • src/boxes/file/mod.rs(または新規 src/boxes/file/handle_box.rs
  • src/boxes/file/provider.rs(必要に応じて)

3.2 Rust 側 struct 定義

/// Phase 110: FileHandleBox
///
/// ハンドルベースのファイル I/O。
/// open(path, mode) → read/write → close() という複数回アクセスをサポート。
pub struct FileHandleBox {
    base: BoxBase,
    path: String,
    mode: String,
    io: Option<Arc<dyn FileIo>>,  // 各インスタンスが独立した FileIo を保持
}

3.3 NyashBox 実装

impl NyashBox for FileHandleBox {
    fn type_name(&self) -> &str {
        "FileHandleBox"
    }

    fn to_string_box(&self) -> StringBox {
        StringBox::new(format!("FileHandleBox(path={}, mode={}, open={})",
            self.path, self.mode, self.is_open()))
    }

    fn equals(&self, _other: &dyn NyashBox) -> bool {
        // FileHandleBox インスタンスは path + mode で比較
        // 厳密でなくて OK
        false  // 簡易実装
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

3.4 メソッドセット

impl FileHandleBox {
    /// 新規 FileHandleBox を作成(ファイルはまだ open されない)
    pub fn new() -> Self {
        FileHandleBox {
            base: BoxBase::new(),
            path: String::new(),
            mode: String::new(),
            io: None,
        }
    }

    /// ファイルを開く
    ///
    /// # Arguments
    /// - path: ファイルパス
    /// - mode: "r" (読み込み) or "w" (上書き) ※ Phase 111 で "a" (append) 追加予定
    ///
    /// # Error
    /// - 既に open されている場合: "FileHandleBox is already open. Call close() first."
    /// - ファイルが見つからない場合mode="r": "File not found"
    pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> {
        // 二重 open チェック
        if self.io.is_some() {
            return Err("FileHandleBox is already open. Call close() first.".to_string());
        }

        // mode 検証
        if mode != "r" && mode != "w" {
            return Err(format!("Unsupported mode: {}. Use 'r' or 'w'", mode));
        }

        // 新規 FileIo 作成Ring0FsFileIo
        // ※ provider_lock から取得するか、直接生成するかは実装で決定
        let io = Arc::new(Ring0FsFileIo::new(ring0.clone()));
        io.open(path)?;

        self.path = path.to_string();
        self.mode = mode.to_string();
        self.io = Some(io);
        Ok(())
    }

    /// ファイルの内容を全て読む
    ///
    /// # Error
    /// - open されていない場合: "FileHandleBox is not open"
    pub fn read_to_string(&self) -> Result<String, String> {
        self.io.as_ref()
            .ok_or("FileHandleBox is not open".to_string())?
            .read()
    }

    /// ファイルに内容を書き込む(上書きモード)
    ///
    /// # Error
    /// - open されていない場合: "FileHandleBox is not open"
    /// - write mode でない場合: "FileHandleBox opened in read mode"
    pub fn write_all(&self, content: &str) -> Result<(), String> {
        if self.mode != "w" {
            return Err("FileHandleBox opened in read mode".to_string());
        }

        self.io.as_ref()
            .ok_or("FileHandleBox is not open".to_string())?
            .write(content)
    }

    /// ファイルを閉じる
    ///
    /// # Error
    /// - open されていない場合: "FileHandleBox is not open"
    pub fn close(&mut self) -> Result<(), String> {
        if self.io.is_none() {
            return Err("FileHandleBox is not open".to_string());
        }

        // 内部 FileIo を drop
        self.io.take();
        self.path.clear();
        self.mode.clear();
        Ok(())
    }

    /// ファイルが open されているかチェック
    pub fn is_open(&self) -> bool {
        self.io.is_some()
    }
}

3.5 Ring0FsFileIo の独立性(重要)

設計原則:

  • 各 FileHandleBox インスタンスが 独立した FileIo を保持
  • 複数の FileHandleBox が同時に異なるファイルを open できる

:

let mut h1 = FileHandleBox::new();
h1.open("/tmp/file1.txt", "r")?;

let mut h2 = FileHandleBox::new();
h2.open("/tmp/file2.txt", "w")?;

// h1 と h2 は別々の FileIoRing0FsFileIoを持つ
// h1.read() と h2.write() は同時実行可能

h1.close()?;
h2.close()?;

4. Task 3: プロファイルとの整合

4.1 ファイル

  • src/runtime/provider_lock.rs(必要に応じて)
  • src/runtime/runtime_profile.rs
  • docs/development/current/main/phase110_filehandlebox_design.md(このドキュメント)

4.2 FileHandleBox のプロファイル位置づけ

RuntimeProfile::Default:

  • 位置づけ: optionalコアボックスではない
  • 動作:
    • provider_lock に FileIo が登録されている
    • FileHandleBox.open() は成功する
    • read/write 可能

RuntimeProfile::NoFs:

  • 位置づけ: 使用不可disabled
  • 動作:
    • provider_lock に FileIo が登録されないNoFsFileIo のみ)
    • FileHandleBox.open() を呼ぶと:
      Err("File I/O disabled in no-fs profile. FileHandleBox is not available.")
      

将来計画Phase 113+:

  • RuntimeProfile::TestMock: mock FileIo を使用(テスト用)
  • RuntimeProfile::Sandbox: 指定ディレクトリのみアクセス可能
  • RuntimeProfile::ReadOnly: 読み取り専用write Err

4.3 Policy 記述

## プロファイル別の可用性

| Profile  | FileBox | FileHandleBox | 特性 |
|----------|---------|---------------|------|
| Default  | ✅ | ✅ | 完全なファイル I/O |
| NoFs     | ❌ | ❌ | ファイル I/O 禁止 |
| TestMock (TBD) | ✅ mock | ✅ mock | テスト用ダミー |
| Sandbox (TBD)  | ✅ dir限定 | ✅ dir限定 | サンドボックス |

**注意**: FileHandleBox は optional boxes だが、NoFs では
「disabled」として扱うoptional ではなく「使用不可」)

5. Task 4: 最小限のテストとサンプル

5.1 ファイル

  • src/boxes/file/tests.rs(新規 or 既存に追加)
  • apps/examples/file_handle_min.hako(オプション、.hako 側のサンプル)

5.2 テスト内容

テスト 1: Default プロファイル - 基本動作

#[test]
fn test_filehandlebox_basic_write_read() {
    // 新しいファイルに書き込み
    let mut h = FileHandleBox::new();
    assert!(!h.is_open());

    let tmp_path = "/tmp/phase110_test_write_read.txt";
    h.open(tmp_path, "w").expect("open failed");
    assert!(h.is_open());

    h.write_all("hello world").expect("write failed");
    h.close().expect("close failed");
    assert!(!h.is_open());

    // 同じファイルを読む
    let mut h2 = FileHandleBox::new();
    h2.open(tmp_path, "r").expect("open failed");
    let content = h2.read_to_string().expect("read failed");
    assert_eq!(content, "hello world");
    h2.close().expect("close failed");

    // cleanup
    std::fs::remove_file(tmp_path).ok();
}

テスト 2: 二重 open error

#[test]
fn test_filehandlebox_double_open_error() {
    let mut h = FileHandleBox::new();
    h.open("/tmp/test.txt", "w").expect("first open");

    // 2 番目の open は error
    let result = h.open("/tmp/test2.txt", "w");
    assert!(result.is_err());
    assert!(result.unwrap_err().contains("already open"));
}

テスト 3: close() 後のアクセス error

#[test]
fn test_filehandlebox_closed_access_error() {
    let mut h = FileHandleBox::new();
    h.open("/tmp/test.txt", "w").expect("open");
    h.close().expect("close");

    // close 後の read は error
    let result = h.read_to_string();
    assert!(result.is_err());
    assert!(result.unwrap_err().contains("not open"));
}

テスト 4: NoFs プロファイル - open error

#[test]
fn test_filehandlebox_nofs_profile_disabled() {
    // NYASH_RUNTIME_PROFILE=no-fs で実行される想定
    let profile = RuntimeProfile::NoFs;

    let mut h = FileHandleBox::new();
    let result = h.open_with_profile("/tmp/test.txt", "r", &profile);

    assert!(result.is_err());
    assert!(result.unwrap_err().contains("disabled in no-fs profile"));
}

5.3 .hako 側サンプル(オプション)

apps/examples/file_handle_min.hako:

// File handle を使った行単位読み込みのプロトタイプ(将来)
// Phase 110 では不要。Phase 111 以降で追加予定

6. Task 5: ドキュメント更新

6.1 ファイル

  • docs/development/current/main/core_boxes_design.md(修正)
  • docs/development/current/main/ring0-inventory.md(修正)
  • CURRENT_TASK.md(修正)

6.2 やること

core_boxes_design.md 更新

新セクション「5.5 Phase 110 - FileHandleBox」を追加

### 5.5 Phase 110 - FileHandleBox

FileBoxワンショット I/Oを補完するハンドルベースのファイル I/O。

- **位置づけ**: core_optionalfuture で core_required に昇格の可能性)
- **API**: open(path, mode) → read/write → close()
- **プロファイル対応**: Default ✅、NoFs ❌
- **実装**: Ring0FsFileIo を内部で再利用

詳細: [Phase 110 設計書](phase110_filehandlebox_design.md)

ring0-inventory.md 更新

「File I/O Service」セクションの「Future Expansion」に追記

## Future Expansion

- Phase 110: FileHandleBoxハンドルベース複数回アクセス
- Phase 111: append mode サポート
- Phase 112: metadata / stat サポート
- Phase 113: Ring0 service registry 統一化

CURRENT_TASK.md 更新

  • Phase 110 を「計画中 → 進行中」に変更(実装開始時)
  • Phase 110 完了後に「進行中 → 完了」に更新
  • Backlog の「FileHandleBox」項目を消す
  • 次の候補append mode, metadataを記載

7. 実装チェックリストPhase 110

  • phase110_filehandlebox_design.md が存在し、全 5 Task が記述されている
  • Rust 側に FileHandleBox struct が追加されている
  • FileHandleBox が NyashBox を実装している
  • open/read/write/close/is_open メソッドが全て実装されている
  • 二重 open が Err を返すことが確認されている
  • close() 後のアクセスが Err を返すことが確認されている
  • Default プロファイルでの基本動作テストが PASS
  • NoFs プロファイルでの open Err テストが PASS
  • FileBox の既存 API と挙動は変わっていない
  • core_boxes_design.md / ring0-inventory.md / CURRENT_TASK.md が Phase 110 と整合している
  • cargo build --release SUCCESS
  • cargo test --release 全テスト PASS

8. 設計原則Phase 110 で確立)

複数ファイルアクセス パターン

【1つのファイルを複数回】
  local h = new FileHandleBox()
  h.open("/file", "w")
  h.write("data1")
  h.write("data2")
  h.close()

【複数のファイル同時アクセス】
  local h1 = new FileHandleBox()
  h1.open("/file1", "r")

  local h2 = new FileHandleBox()
  h2.open("/file2", "w")

  h1.close()
  h2.close()

Fail-Fast 原則の適用

  • open() 呼び出し時に既に open 済み → 即座に Err
  • close() 後の read/write → 即座に Err
  • NoFs profile で open → 即座に Err

後方互換性

  • FileBox は完全に独立(既存 API 変更なし)
  • FileHandleBox は新規 Box として独立
  • Ring0FsFileIo の変更なし

9. 将来への拡張ポイント

Phase 111: append mode + metadata完了

  • append mode: mode = "a" を実装、末尾に追記可能に
  • metadata API: size / exists / is_file / is_dir を内部 Rust API として実装
  • FsApi.append_all(): write_all と対称的に追加
  • 実装完了: Commit fce7555e で 4 つのテスト全て PASS

Phase 112 以降の計画

  • Phase 112: Ring0 Service Registry 統一化metadata に modified フィールド追加)
  • Phase 113: FileHandleBox NyashBox 公開 API.hako から metadata 呼び出し可能に)
  • Phase 114: FileIo 機能拡張exists/stat/canonicalize を trait に追加)
  • Phase 115: 並行アクセス安全性Arc<Mutex<...>>
  • Phase 116: file encoding explicit 指定UTF-8 以外)

Phase 110 設計書作成日: 2025-12-03修正版 5点統合 Phase 111 完成日: 2025-12-03修正案統合版、4 テスト全 PASS