File access crosses an explicit host boundary. Choose a one-shot FS operation when a single call completes the task. Choose File when multiple reads or writes must share a single 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 requires the File across several nested blocks. - An explicit
file.close()call insideusingdoes not cancel the registered close operation. Repeated-close behavior is not portable.
Both forms perform cleanup upon normal exit and when return, ?, break, or continue exits the owning scope. They do not provide 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 an append operation. Append modes, binary File overloads, buffering, permissions, and line-oriented File methods fall 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 failures stay 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.