diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 006f9c36..8b104268 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -424,7 +424,97 @@ CURRENT_TASK.md 自体は「いまどこを触っているか」と「次に何 - Embedded: 組み込みプロファイル **次のタスク候補**: -- Phase 110: FileHandleBox(複数ファイル同時アクセス) -- Phase 111: append mode 追加 +- ✅ Phase 110: FileHandleBox(複数ファイル同時アクセス) — **完了** (2025-12-03) +- Phase 111: append mode 追加 + FileHandleBox metadata - Phase 112: Ring0 service registry 統一化 +--- + +## Phase 110: FileHandleBox 実装完了 ✅ (2025-12-03) + +### 実装内容 + +**FileHandleBox(ハンドルベース複数回アクセス I/O)の完全実装** + +**ゴール**: FileBox(1ショット I/O)を補完するハンドルベースのファイル I/O を実装 + +**実装完了項目**: +1. ✅ FileHandleBox struct + NyashBox trait 実装 + - `src/boxes/file/handle_box.rs`: 新規作成(450行) + - 独立インスタンス設計(各ハンドルが独自の Ring0FsFileIo を保持) +2. ✅ API 実装 + - `new()` - ハンドル作成(ファイル未open) + - `open(path, mode)` - ファイルを開く("r" or "w") + - `read_to_string()` - ファイル内容読み込み + - `write_all(content)` - ファイル書き込み + - `close()` - ファイルを閉じる + - `is_open()` - open 状態確認 +3. ✅ プロファイル対応 + - Default ✅: Ring0FsFileIo 経由で完全なファイル I/O + - NoFs ❌: open() が即座にエラー +4. ✅ テスト実装(7テスト全PASS) + - 基本的な書き込み・読み込み + - 二重 open エラー + - close 後アクセスエラー + - read mode で write エラー + - 複数回書き込み + - 未サポートモードエラー + - 独立インスタンス動作確認 +5. ✅ ドキュメント更新 + - `core_boxes_design.md`: Section 16 追加 + - `ring0-inventory.md`: Phase 110 完了マーク + - `phase110_filehandlebox_design.md`: 完全仕様(既存) + +**テスト結果**: +- ✅ test_filehandlebox_basic_write_read +- ✅ test_filehandlebox_double_open_error +- ✅ test_filehandlebox_closed_access_error +- ✅ test_filehandlebox_write_wrong_mode +- ✅ test_filehandlebox_multiple_writes +- ✅ test_filehandlebox_unsupported_mode +- ✅ test_filehandlebox_independent_instances +- ✅ cargo build --release: 成功 +- ✅ cargo test --release: 7/7 PASS + +**設計原則**: +- **Fail-Fast**: 二重 open、close 後アクセス、NoFs profile で即座にエラー +- **独立インスタンス**: 各 FileHandleBox が独自の Ring0FsFileIo を保持(複数ファイル同時アクセス可能) +- **Ring0 再利用**: Ring0FsFileIo を内部で使用(レイヤー統一) +- **後方互換性**: FileBox の既存 API 変更なし + +**FileBox との違い**: +- FileBox: 1ショット I/O(read/write 1回ずつ、ファイルを開いて→読む/書く→閉じるを隠す) +- FileHandleBox: 複数回アクセス I/O(open → read/write(複数回可)→ close を明示的に制御) + +**実装詳細**: +```rust +// 各 FileHandleBox が独自の Ring0FsFileIo を作成 +let ring0 = get_global_ring0(); +let io: Arc = Arc::new(Ring0FsFileIo::new(ring0.clone())); + +// Write mode の処理 +if mode == "w" { + // ファイルが存在しない場合は空ファイルを作成 + if !ring0.fs.exists(path_obj) { + ring0.fs.write_all(path_obj, &[])?; + } +} +``` + +**将来拡張予定**: +- Phase 111: append mode ("a") サポート +- Phase 112: file metadata / stat(size, mtime 等) +- Phase 113: Ring0 service registry 統一化 +- Phase 114: 並行アクセス安全性(Arc>) +- Phase 115: file encoding explicit 指定(UTF-8 以外) + +**関連ドキュメント**: +- [Phase 110 設計書](docs/development/current/main/phase110_filehandlebox_design.md) +- [Core Boxes Design](docs/development/current/main/core_boxes_design.md#section-16-phase-110---filehandlebox) +- [Ring0 Inventory](docs/development/current/main/ring0-inventory.md) + +**次のタスク候補**: +- Phase 111: FileHandleBox append mode 追加 + metadata サポート +- Phase 112: Ring0 service registry 統一化 +- Phase 113: FileIo trait 拡張(exists/stat/canonicalize) + diff --git a/docs/development/current/main/core_boxes_design.md b/docs/development/current/main/core_boxes_design.md index 621cb102..9f55de73 100644 --- a/docs/development/current/main/core_boxes_design.md +++ b/docs/development/current/main/core_boxes_design.md @@ -1730,3 +1730,117 @@ NYASH_USE_PLUGIN_HOST=1 ./target/release/hakorune test.hako **Phase 98 実装完了日**: 2025-12-03 **Phase 99 ポリシー確定日**: 2025-12-03 + +--- + +## Section 16: Phase 110 - FileHandleBox(ハンドルベース複数回アクセス I/O) + +### 概要 + +FileBox(ワンショット I/O)を補完するハンドルベースのファイル I/O。 + +- **位置づけ**: core_optional(future で core_required に昇格の可能性) +- **API**: open(path, mode) → read/write → close() +- **プロファイル対応**: Default ✅、NoFs ❌ +- **実装**: Ring0FsFileIo を内部で再利用 + +### 設計原則 + +**FileBox(Phase 108)との違い**: +- FileBox: 1ショット I/O(read/write を1回ずつ、ファイルを開いて→読む/書く→閉じるを隠す) +- FileHandleBox: 複数回アクセス I/O(open → read/write(複数回可)→ close を明示的に制御) + +**Fail-Fast 原則**: +- open() 呼び出し時に既に open 済み → 即座に Err +- close() 後の read/write → 即座に Err +- NoFs profile で open → 即座に Err + +**独立インスタンス設計**: +- 各 FileHandleBox インスタンスが独立した Ring0FsFileIo を保持 +- 複数の FileHandleBox が同時に異なるファイルを open 可能 + +### API + +```rust +pub struct FileHandleBox { + base: BoxBase, + path: String, + mode: String, + io: Option>, +} + +impl FileHandleBox { + pub fn new() -> Self; + pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String>; + pub fn read_to_string(&self) -> Result; + pub fn write_all(&self, content: &str) -> Result<(), String>; + pub fn close(&mut self) -> Result<(), String>; + pub fn is_open(&self) -> bool; +} +``` + +### サポートモード + +**Phase 110**: +- "r" (read): ファイル読み込み専用 +- "w" (write): ファイル上書き書き込み(truncate mode) + +**Phase 111 予定**: +- "a" (append): ファイル追記モード + +### プロファイル対応 + +| Profile | FileHandleBox | 動作 | +|----------|---------------|------| +| Default | ✅ | Ring0FsFileIo を使用(完全なファイル I/O) | +| NoFs | ❌ | open() が Err を返す("File I/O disabled in no-fs profile") | +| TestMock (TBD) | ✅ mock | テスト用ダミー | +| Sandbox (TBD) | ✅ dir限定 | サンドボックス内のみアクセス可能 | + +### 実装詳細 + +**ファイル**: `src/boxes/file/handle_box.rs` + +**主要機能**: +1. new() - ハンドル作成(ファイルは未open) +2. open(path, mode) - ファイルを開く + - 二重 open チェック(Fail-Fast) + - mode 検証("r" or "w") + - NoFs profile チェック + - Ring0FsFileIo インスタンス作成 +3. read_to_string() - ファイル内容を読み込み +4. write_all(content) - ファイルに書き込み + - write mode チェック +5. close() - ファイルを閉じる + - FileIo インスタンスを drop +6. is_open() - ファイルが open されているかチェック + +### テスト + +**実装済みテスト** (7個): +1. test_filehandlebox_basic_write_read - 基本的な書き込み・読み込み +2. test_filehandlebox_double_open_error - 二重 open エラー +3. test_filehandlebox_closed_access_error - close 後アクセスエラー +4. test_filehandlebox_write_wrong_mode - read mode で write エラー +5. test_filehandlebox_multiple_writes - 複数回書き込み +6. test_filehandlebox_unsupported_mode - 未サポートモードエラー +7. test_filehandlebox_independent_instances - 独立インスタンス動作確認 + +**テスト結果**: ✅ 7/7 PASS + +### 将来の拡張ポイント + +- **Phase 111**: append mode (`mode = "a"`) +- **Phase 112**: file metadata / stat(size, mtime 等) +- **Phase 113**: Ring0 service registry 統一化 +- **Phase 114**: 並行アクセス安全性(Arc>) +- **Phase 115**: file encoding explicit 指定(UTF-8 以外) + +### 関連ドキュメント + +- [Phase 110 設計書](phase110_filehandlebox_design.md) - 完全仕様 +- [Ring0 Inventory](ring0-inventory.md) - FileIo/FsApi レイヤー設計 + +--- + +**Phase 110 実装完了日**: 2025-12-03 diff --git a/docs/development/current/main/ring0-inventory.md b/docs/development/current/main/ring0-inventory.md index b8fd3e27..54c55b32 100644 --- a/docs/development/current/main/ring0-inventory.md +++ b/docs/development/current/main/ring0-inventory.md @@ -579,13 +579,18 @@ Phase 106–108 では FileBox provider_lock / Ring0FsFileIo / write/write_all 今後の候補フェーズ(優先度の高い順): -- **Phase 109: runtime profiles(default/no-fs)** +- ✅ **Phase 109: runtime profiles(default/no-fs)** (COMPLETED) - `NYASH_PROFILE={default|no-fs}` などのプロファイル導入 - default では FileBox 必須、no-fs では FileBox provider 未登録(エラー文字列で通知) -- **Phase 110: FileHandleBox** +- ✅ **Phase 110: FileHandleBox** (COMPLETED - 2025-12-03) - FileBox を「1ファイル専用」に保ちつつ、複数ファイル同時アクセスは FileHandleBox 側に切り出す - Ring0FsFileIo を再利用してハンドル単位の管理を行う -- **Phase 111: Fs metadata 拡張** + - ハンドルベース複数回アクセス I/O 完全実装 + - 実装: `src/boxes/file/handle_box.rs` (7テスト全PASS) + - API: open(path, mode) → read/write → close() + - プロファイル対応: Default ✅、NoFs ❌ +- **Phase 111: Fs metadata 拡張 + append mode** + - FileHandleBox に append mode ("a") を追加 - `exists/metadata/canonicalize` を FileIo / FileBox 側にきちんとエクスポート - Ring0.FsApi の stat 情報を Nyash 側から扱えるようにする diff --git a/src/boxes/file/handle_box.rs b/src/boxes/file/handle_box.rs new file mode 100644 index 00000000..273fc5e1 --- /dev/null +++ b/src/boxes/file/handle_box.rs @@ -0,0 +1,477 @@ +//! Phase 110: FileHandleBox - Handle-based file I/O +//! +//! Provides multiple-access file I/O pattern: +//! - open(path, mode) → read/write (multiple times) → close() +//! +//! Complements FileBox (one-shot I/O) by allowing multiple reads/writes +//! to the same file handle. + +use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; +use crate::boxes::file::provider::FileIo; +use crate::runtime::provider_lock; +use std::any::Any; +use std::sync::Arc; + +/// Phase 110: FileHandleBox +/// +/// Handle-based file I/O for multiple-access patterns. +/// +/// # Lifecycle +/// +/// 1. new() - Create handle (file not yet opened) +/// 2. open(path, mode) - Open file (stores FileIo instance) +/// 3. read_to_string() / write_all() - Multiple accesses allowed +/// 4. close() - Close file (resets FileIo) +/// +/// # Design Principles +/// +/// - **Fail-Fast**: Double open() returns Err +/// - **Independent instances**: Each FileHandleBox has its own FileIo +/// - **Profile-aware**: NoFs profile → open() returns Err +/// - **Ring0 reuse**: Uses Ring0FsFileIo internally +pub struct FileHandleBox { + base: BoxBase, + /// Current file path (empty if not open) + path: String, + /// Current file mode ("r" or "w", empty if not open) + mode: String, + /// FileIo instance (None if not open) + io: Option>, +} + +impl std::fmt::Debug for FileHandleBox { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FileHandleBox") + .field("path", &self.path) + .field("mode", &self.mode) + .field("is_open", &self.is_open()) + .finish() + } +} + +impl Clone for FileHandleBox { + fn clone(&self) -> Self { + // Clone creates a new independent handle (not open) + // Design decision: Cloning an open handle creates a closed handle + // Rationale: Prevents accidental file handle leaks + FileHandleBox { + base: BoxBase::new(), + path: String::new(), + mode: String::new(), + io: None, + } + } +} + +impl FileHandleBox { + /// Create new FileHandleBox (file not yet opened) + pub fn new() -> Self { + FileHandleBox { + base: BoxBase::new(), + path: String::new(), + mode: String::new(), + io: None, + } + } + + /// Open a file + /// + /// # Arguments + /// + /// - path: File path to open + /// - mode: "r" (read) or "w" (write) + /// + /// # Errors + /// + /// - Already open: "FileHandleBox is already open. Call close() first." + /// - Unsupported mode: "Unsupported mode: X. Use 'r' or 'w'" + /// - NoFs profile: "File I/O disabled in no-fs profile. FileHandleBox is not available." + /// - File not found (mode="r"): "File not found: PATH" + /// + /// # Design Notes + /// + /// - Phase 110: Supports "r" and "w" only + /// - Phase 111: Will add "a" (append) mode + /// - Each FileHandleBox instance gets its own FileIo (independent) + pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> { + // Fail-Fast: Check for double open + if self.io.is_some() { + return Err("FileHandleBox is already open. Call close() first.".to_string()); + } + + // Validate mode + if mode != "r" && mode != "w" { + return Err(format!("Unsupported mode: {}. Use 'r' or 'w'", mode)); + } + + // Get FileIo provider to check capabilities + let provider = provider_lock::get_filebox_provider() + .ok_or("FileBox provider not initialized")?; + + // NoFs profile check (Fail-Fast) + let caps = provider.caps(); + if !caps.read && !caps.write { + return Err("File I/O disabled in no-fs profile. FileHandleBox is not available." + .to_string()); + } + + // Mode-specific capability check + if mode == "r" && !caps.read { + return Err("Read not supported by FileBox provider".to_string()); + } + if mode == "w" && !caps.write { + return Err("Write not supported by FileBox provider".to_string()); + } + + // Create NEW independent Ring0FsFileIo instance for this handle + // IMPORTANT: We must create a new instance, not clone the Arc + // because Ring0FsFileIo has internal state (path field) + use crate::runtime::get_global_ring0; + use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + + let ring0 = get_global_ring0(); + + // For write mode, create the file if it doesn't exist + // Ring0FsFileIo expects the file to exist, so we create it first + if mode == "w" { + use std::path::Path; + let path_obj = Path::new(path); + if !ring0.fs.exists(path_obj) { + // Create empty file for write mode + ring0 + .fs + .write_all(path_obj, &[]) + .map_err(|e| format!("Failed to create file: {}", e))?; + } + } + + let io: Arc = Arc::new(Ring0FsFileIo::new(ring0.clone())); + + // Now open the file with the new instance + io.open(path) + .map_err(|e| format!("Failed to open file: {}", e))?; + + // Store state + self.path = path.to_string(); + self.mode = mode.to_string(); + self.io = Some(io); + + Ok(()) + } + + /// Read file contents to string + /// + /// # Errors + /// + /// - Not open: "FileHandleBox is not open" + /// - Read failed: "Read failed: ERROR" + pub fn read_to_string(&self) -> Result { + self.io + .as_ref() + .ok_or("FileHandleBox is not open".to_string())? + .read() + .map_err(|e| format!("Read failed: {}", e)) + } + + /// Write content to file + /// + /// # Errors + /// + /// - Not open: "FileHandleBox is not open" + /// - Wrong mode: "FileHandleBox opened in read mode" + /// - Write failed: "Write failed: ERROR" + pub fn write_all(&self, content: &str) -> Result<(), String> { + // Fail-Fast: Check mode + 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) + .map_err(|e| format!("Write failed: {}", e)) + } + + /// Close the file + /// + /// # Errors + /// + /// - Not open: "FileHandleBox is not open" + /// + /// # Post-condition + /// + /// After close(), is_open() returns false and read/write will fail. + pub fn close(&mut self) -> Result<(), String> { + if self.io.is_none() { + return Err("FileHandleBox is not open".to_string()); + } + + // Drop the FileIo instance + self.io.take(); + self.path.clear(); + self.mode.clear(); + + Ok(()) + } + + /// Check if file is currently open + pub fn is_open(&self) -> bool { + self.io.is_some() + } +} + +impl BoxCore for FileHandleBox { + fn box_id(&self) -> u64 { + self.base.id + } + + fn parent_type_id(&self) -> Option { + self.base.parent_type_id + } + + fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "FileHandleBox(path={}, mode={}, open={})", + if self.path.is_empty() { + "" + } else { + &self.path + }, + if self.mode.is_empty() { + "" + } else { + &self.mode + }, + self.is_open() + ) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +impl NyashBox for FileHandleBox { + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn share_box(&self) -> Box { + self.clone_box() + } + + fn to_string_box(&self) -> StringBox { + StringBox::new(format!( + "FileHandleBox(path={}, mode={}, open={})", + self.path, self.mode, self.is_open() + )) + } + + fn type_name(&self) -> &'static str { + "FileHandleBox" + } + + fn equals(&self, other: &dyn NyashBox) -> BoolBox { + if let Some(other_handle) = other.as_any().downcast_ref::() { + // Equality: Same path and mode + // Note: Two independent handles to same file are equal if path/mode match + BoolBox::new(self.path == other_handle.path && self.mode == other_handle.mode) + } else { + BoolBox::new(false) + } + } +} + +impl std::fmt::Display for FileHandleBox { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.fmt_box(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::ring0::default_ring0; + use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + use std::fs; + use std::io::Write; + use std::sync::Arc; + + fn setup_test_file(path: &str, content: &str) { + let mut file = fs::File::create(path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); + } + + fn cleanup_test_file(path: &str) { + let _ = fs::remove_file(path); + } + + /// Helper: Initialize FileBox provider for tests + fn init_test_provider() { + // Try to initialize Ring0 (ignore if already initialized) + use crate::runtime::ring0::init_global_ring0; + use std::panic; + + let _ = panic::catch_unwind(|| { + let ring0 = default_ring0(); + init_global_ring0(ring0); + }); + + // Set provider if not already set (ignore errors from re-initialization) + let ring0_arc = Arc::new(default_ring0()); + let provider = Arc::new(Ring0FsFileIo::new(ring0_arc)); + let _ = provider_lock::set_filebox_provider(provider); + } + + #[test] + fn test_filehandlebox_basic_write_read() { + init_test_provider(); + + let tmp_path = "/tmp/phase110_test_write_read.txt"; + + // Write to file + let mut h = FileHandleBox::new(); + assert!(!h.is_open()); + + h.open(tmp_path, "w").expect("open for write failed"); + assert!(h.is_open()); + + h.write_all("hello world").expect("write failed"); + h.close().expect("close failed"); + assert!(!h.is_open()); + + // Read from file (separate instance) + let mut h2 = FileHandleBox::new(); + h2.open(tmp_path, "r").expect("open for read failed"); + let content = h2.read_to_string().expect("read failed"); + assert_eq!(content, "hello world"); + h2.close().expect("close failed"); + + cleanup_test_file(tmp_path); + } + + #[test] + fn test_filehandlebox_double_open_error() { + init_test_provider(); + + let tmp_path = "/tmp/phase110_test_double_open.txt"; + setup_test_file(tmp_path, "test"); + + let mut h = FileHandleBox::new(); + h.open(tmp_path, "r").expect("first open"); + + // Second open should fail + let result = h.open(tmp_path, "r"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("already open")); + + h.close().expect("close"); + cleanup_test_file(tmp_path); + } + + #[test] + fn test_filehandlebox_closed_access_error() { + init_test_provider(); + + let tmp_path = "/tmp/phase110_test_closed_access.txt"; + setup_test_file(tmp_path, "test"); + + let mut h = FileHandleBox::new(); + h.open(tmp_path, "r").expect("open"); + h.close().expect("close"); + + // Read after close should fail + let result = h.read_to_string(); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("not open")); + + cleanup_test_file(tmp_path); + } + + #[test] + fn test_filehandlebox_write_wrong_mode() { + init_test_provider(); + + let tmp_path = "/tmp/phase110_test_write_wrong_mode.txt"; + setup_test_file(tmp_path, "test"); + + let mut h = FileHandleBox::new(); + h.open(tmp_path, "r").expect("open for read"); + + // Write in read mode should fail + let result = h.write_all("data"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("read mode")); + + h.close().expect("close"); + cleanup_test_file(tmp_path); + } + + #[test] + fn test_filehandlebox_multiple_writes() { + init_test_provider(); + + let tmp_path = "/tmp/phase110_test_multiple_writes.txt"; + + let mut h = FileHandleBox::new(); + h.open(tmp_path, "w").expect("open"); + + // Multiple writes (note: current design is truncate mode) + h.write_all("first write").expect("first write"); + // Second write will overwrite (truncate mode) + h.write_all("second write").expect("second write"); + + h.close().expect("close"); + + // Verify final content + let content = fs::read_to_string(tmp_path).unwrap(); + assert_eq!(content, "second write"); + + cleanup_test_file(tmp_path); + } + + #[test] + fn test_filehandlebox_unsupported_mode() { + init_test_provider(); + + let mut h = FileHandleBox::new(); + let result = h.open("/tmp/test.txt", "a"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Unsupported mode")); + } + + #[test] + fn test_filehandlebox_independent_instances() { + init_test_provider(); + + let tmp_path1 = "/tmp/phase110_test_independent1.txt"; + let tmp_path2 = "/tmp/phase110_test_independent2.txt"; + setup_test_file(tmp_path1, "file1"); + setup_test_file(tmp_path2, "file2"); + + // Two independent handles + let mut h1 = FileHandleBox::new(); + let mut h2 = FileHandleBox::new(); + + h1.open(tmp_path1, "r").expect("h1 open"); + h2.open(tmp_path2, "r").expect("h2 open"); + + let content1 = h1.read_to_string().expect("h1 read"); + let content2 = h2.read_to_string().expect("h2 read"); + + assert_eq!(content1, "file1"); + assert_eq!(content2, "file2"); + + h1.close().expect("h1 close"); + h2.close().expect("h2 close"); + + cleanup_test_file(tmp_path1); + cleanup_test_file(tmp_path2); + } +} diff --git a/src/boxes/file/mod.rs b/src/boxes/file/mod.rs index 34d15e56..3e1a4125 100644 --- a/src/boxes/file/mod.rs +++ b/src/boxes/file/mod.rs @@ -10,8 +10,12 @@ pub mod box_shim; // Thin delegating shim pub mod builtin_factory; pub mod core_ro; // Core read‑only provider +pub mod handle_box; // Phase 110: FileHandleBox (handle-based multiple-access I/O) pub mod provider; // trait FileIo / FileCaps / FileError // Builtin FileBox ProviderFactory +// Re-export FileHandleBox for easier access +pub use handle_box::FileHandleBox; + use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use crate::runtime::provider_lock; use std::any::Any;