feat(phase110): FileHandleBox実装(ハンドルベース複数回アクセス)

Phase 110 完全実装完了:
- FileHandleBox struct で open → read/write → close の複数回アクセスをサポート
- 各インスタンスが独立した Ring0FsFileIo を保持
- Fail-Fast 原則:二重 open、close後アクセス、NoFsプロファイル全て即座にエラー
- RuntimeProfile 対応:Default で使用可能、NoFs で禁止

実装ファイル:
- src/boxes/file/handle_box.rs (新規, 450行)
- src/boxes/file/mod.rs (修正, export追加)

テスト: 7/7 PASS 
- 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

ドキュメント更新:
- core_boxes_design.md (Section 16 追加)
- ring0-inventory.md (Phase 110完了)
- CURRENT_TASK.md (Phase 110完了セクション)

設計原則:
- FileBox: ワンショット I/O(open/close隠蔽)
- FileHandleBox: ハンドルベース I/O(複数回アクセス対応)
- Ring0FsFileIo を内部で再利用

次フェーズ: Phase 111(append mode + metadata)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-03 20:28:33 +09:00
parent bc1fce3dfe
commit acd1b54ef9
5 changed files with 695 additions and 5 deletions

View File

@ -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の完全実装**
**ゴール**: FileBox1ショット 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/Oread/write 1回ずつ、ファイルを開いて→読む/書く→閉じるを隠す)
- FileHandleBox: 複数回アクセス I/Oopen → read/write複数回可→ close を明示的に制御)
**実装詳細**:
```rust
// 各 FileHandleBox が独自の Ring0FsFileIo を作成
let ring0 = get_global_ring0();
let io: Arc<dyn FileIo> = 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 / statsize, mtime 等)
- Phase 113: Ring0 service registry 統一化
- Phase 114: 並行アクセス安全性Arc<Mutex<...>>
- 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

View File

@ -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_optionalfuture で core_required に昇格の可能性)
- **API**: open(path, mode) → read/write → close()
- **プロファイル対応**: Default ✅、NoFs ❌
- **実装**: Ring0FsFileIo を内部で再利用
### 設計原則
**FileBoxPhase 108との違い**:
- FileBox: 1ショット I/Oread/write を1回ずつ、ファイルを開いて→読む/書く→閉じるを隠す)
- FileHandleBox: 複数回アクセス I/Oopen → 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<Arc<dyn FileIo>>,
}
impl FileHandleBox {
pub fn new() -> Self;
pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String>;
pub fn read_to_string(&self) -> Result<String, String>;
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 / statsize, mtime 等)
- **Phase 113**: Ring0 service registry 統一化
- **Phase 114**: 並行アクセス安全性Arc<Mutex<...>>
- **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

View File

@ -579,13 +579,18 @@ Phase 106108 では FileBox provider_lock / Ring0FsFileIo / write/write_all
今後の候補フェーズ(優先度の高い順):
- **Phase 109: runtime profilesdefault/no-fs**
- **Phase 109: runtime profilesdefault/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 側から扱えるようにする

View File

@ -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<Arc<dyn FileIo>>,
}
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<dyn FileIo> = 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<String, String> {
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<std::any::TypeId> {
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() {
"<none>"
} else {
&self.path
},
if self.mode.is_empty() {
"<none>"
} 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<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
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::<FileHandleBox>() {
// 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);
}
}

View File

@ -10,8 +10,12 @@
pub mod box_shim; // Thin delegating shim
pub mod builtin_factory;
pub mod core_ro; // Core readonly 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;