File access crosses an explicit host boundary. Choose a one-shot FS operation
when one call completes the task. Choose File when several reads or writes
must share one open resource and a visible lifetime.
Choose FS or File
| Task | Use | Exact result |
|---|---|---|
| Read a text file in one call | FS.readText(path) | Result<string, string> |
| Replace a text file in one call | FS.writeText(path, text) | Result<(), string> |
| Read or write immutable bytes | FS.readBytes, FS.writeBytes | Result<Bytes, string> or Result<(), string> |
| List one directory | FS.list(path) | Result<Array<{ kind: string, name: string, sizeBytes: Option<int> }>, string> |
| Keep one file open for several operations | open(path) | Result<File, string> |
The FS calls accept Path | string. The lifetime-bearing surface is
deliberately smaller:
open(path: string) -> Result<File, string>
file.read() -> Result<string, string>
file.write(text: string) -> Result<(), string>
file.close() -> ()There is no canonical File.open, File.create, File.append, readLine,
writeLine, or flush.
Read through one managed File
Create note.txt containing ready, then save this program as
files-resources.tpz:
function readNote(path: string) -> Result<string, string> {
let mut result: Result<string, string> = Err("not read")
using file = open(path)? {
result = file.read()
}
return result
}
match readNote("note.txt") {
case Ok(text) => print(text.trim())
case Err(error) => print(error)
}readyopen may fail before a File exists, so it returns Result. Postfix ?
propagates that error before cleanup is registered. After acquisition,
using creates one immutable File binding in its child scope and registers
one implicit close. The result is copied to the outer mutable binding before
the scope closes.
Choose using or defer
- Prefer
using file = open(path)? { ... }when one block owns exactly one File. The binding cannot escape its child scope as an open lifetime. - Prefer
let file = open(path)?followed immediately bydefer { file.close() }when the owning function needs the File across several nested blocks. - An explicit
file.close()insideusingdoes not cancel the registered close. Repeated-close behavior is not portable.
Both forms clean up on normal exit and when return, ?, break, or
continue crosses the owning scope. They are not a general finally
guarantee: an ordinary runtime fault aborts evaluation without a language
promise that lexical cleanup runs.
Common correction: do not describe file.write as append. Append modes,
binary File overloads, buffering, permissions, and line-oriented File methods
are outside the portable File contract. Use the exact FS byte operations
when whole-file binary data is the task.
Capability and value boundaries
File,FS,std.fs, andstd.iorequire a host profile with filesystem access.- Importing a module grants no path, environment, process, network, or database authority.
- Acquisition, read, and write failure stays in
Result; it is not a catchable exception. Fileis opaque, non-comparable, and has no portable post-close behavior beyond the listed signatures.- User-defined disposable protocols, multiple-resource
using, asynchronous cleanup, cancellation cleanup, and module-lifetime finalization are not current language contracts.
Continue with Defer & Resources, Null, Option, Result & Faults, or Standard Library Overview.