Module fs/file
Async file I/O operations with Exception-based error handling.
Wraps a file descriptor with typed async I/O operations.
Uses the Exception effect for error handling.
Example
{ File, read_to_string, write_string, OpenMode } :: import "std/fs/file";
main :: (fn(io : Io, exn : Exception) -> unit)({
// Write a file
io.await(write_string(Path.new(`test.txt`), `hello world`, io), { io, exn });
// Read it back
content := io.await(read_to_string(Path.new(`test.txt`), io), { io, exn });
println(content);
// Open with OpenMode
f := io.await(File.open(Path.new(`test.txt`), .Read, io), { io, exn });
});
Positions are per-handle, not per-descriptor
Every read and write goes through POSITIONAL I/O (pread/pwrite), so
the descriptor's own file offset never moves and File._pos is the only
authority on "where am I". Two consequences a caller cannot see from the
signatures: seek/position are pure arithmetic on the handle and never
reach the OS (which is why seek needs only exn, not io), and a
File is a ref handle, so a copy of one SHARES the position — two
interleaved async reads through the same handle read from wherever the
other one left off, they do not each get their own cursor.
Stability
unstable — flush names the wrong operation. The inherent
File.flush(io) is an fsync, which is Rust's File::sync_all, while
this file's io.Writer impl gives the same spelling Rust's
Write::flush meaning: a no-op, because an fd write is unbuffered. So
f.flush(io) costs a disk barrier while the identical call inside a
function bounded on Writer costs nothing, and nothing in the signature
says which one a reader is looking at. Freezing follows splitting those
into sync_all / sync_data (fdatasync) with flush left to the
trait, not a release count.
Types
An open file handle with async read/write operations.
Provides methods for reading, writing, seeking, and querying file metadata. Files are automatically closed when disposed.
Fields
| Name | Type | Description |
|---|---|---|
_fd | i32 | |
_path | Path | |
_is_closed | bool | |
_pos | u64 | Byte offset for the NEXT
One exception is deliberate: a handle opened |
Trait Implementations
impl(File, ...)
open_with : (File) fn(path : Path, mode : OpenMode, perm : FilePermission, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)open_with_str : (File) fn(path : str, mode : OpenMode, perm : FilePermission, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)open_with_cstr : (File) fn(path : *(u8), mode : OpenMode, perm : FilePermission, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)open : (File) fn(path : Path, mode : OpenMode, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)open_str : (File) fn(path : str, mode : OpenMode, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)open_opts_with : (File) fn(path : Path, opts : OpenOptions, perm : FilePermission, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)Open a file at path with an OpenOptions flag set and custom
permissions.
perm is consulted only when opts.creates() — POSIX ignores the mode
argument otherwise, so passing one for a non-creating open is silently
meaningless and this mirrors that.
Throws when opts names a contradictory combination (see
OpenOptions.to_flags): the thrown error is a Context carrying the
explanation over an IoError.InvalidInput, so to_string() says which
combination was rejected while source() still reports the kind Rust
would report.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
path | Path | Get the path this file was opened with. | |
opts | OpenOptions | ||
perm | FilePermission | ||
io | Io |
open_opts : (File) fn(path : Path, opts : OpenOptions, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)open_cstr : (File) fn(path : *(u8), mode : OpenMode, io : Io) -> Impl : (Future[Future](File) IoExn : IoExn)read : (File) fn(self : File, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)write : (File) fn(self : File, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)write_string : (File) fn(self : File, data : String, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)write_bytes : (File) fn(self : File, data : ArrayList(u8), io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)flush : (File) fn(self : File, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)set_len : (File) fn(self : File, length : i64, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)from_fd : (File) fn(fd : i32) -> FileWrap an ALREADY-OPEN file descriptor without opening anything (Rust's
File::from for raw fds). The handle starts at position 0 — use
seek() if the descriptor's data should be read from elsewhere. The
path is unknown, so path() reports an empty path.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
fd | i32 | Get the underlying file descriptor. |
Returns: File
seek : (File) fn(self : File, offset : i64, from : SeekFrom, exn : Exception) -> i64position : (File) fn(self : File) -> i64Get the current file position (byte offset from the beginning).
Reports _pos, not the descriptor's position. This used to ask the
descriptor via lseek(SEEK_CUR), which ALWAYS answered 0: positional
reads and writes never move it.
Parameters
| Name | Type | Notes |
|---|---|---|
self | File |
Returns: i64
rewind : (File) fn(self : File, exn : Exception) -> unitsize : (File) fn(self : File) -> i64close : (File) fn(self : File, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)fd : (File) fn(self : File) -> i32path : (File) fn(self : File) -> Pathmetadata : (File) fn(self : File, io : Io) -> Impl : (Future[Future](Metadata) IoExn : IoExn)Get file metadata (size, permissions, timestamps).
fstats THIS handle's descriptor. It used to re-stat _path, which
answered about a different inode the moment the two diverged: a
from_fd handle has no path at all and an empty Path renders as .,
so it reported the CURRENT DIRECTORY's size, mode and times with no
error — issues/fixed/fs-metadata-restats-by-path-and-walker-drops-dt-unknown.md.
A file renamed, replaced or unlinked after the open had the same
problem. The descriptor names the inode; the path does not.
Parameters
| Name | Type | Notes |
|---|---|---|
self | File | |
io | Io |
impl(File, Dispose(...))
dispose : (File) fn(self : File) -> unitRelease the resources self owns — a file descriptor, a socket, a lock,
a buffer the allocator handed out. Called automatically when the last
reference to the value goes away, so an implementor never calls it
directly and must tolerate being the only one who ever does.
It must be safe to run exactly once: the runtime calls it at refcount
zero, and a type that also exposes an explicit close/release is
responsible for making the second call a no-op.
Parameters
| Name | Type | Notes |
|---|---|---|
self | File |
Returns: unit
impl(File, IoTraits)
impl(File, IoTraits)
impl(File, IoTraits)
Methods
read_to_end : (File) fn(self : File, io : Io) -> Impl : (Future[Future](ArrayList(u8)) IoExn : IoExn)read_to_string : (File) fn(self : File, io : Io) -> Impl : (Future[Future](String) IoExn : IoExn)Determines how a file is opened.
Variants
| Variant | Fields | Description |
|---|---|---|
Read | Read an existing file (O_RDONLY). | |
Write | Create or overwrite a file (O_WRONLY | O_CREAT | O_TRUNC). | |
Append | Append to a file (O_WRONLY | O_CREAT | O_APPEND). | |
ReadWrite | Read and write an existing file (O_RDWR). | |
CreateNew | Create a new file, fail if it already exists (O_WRONLY | O_CREAT | O_EXCL). |
A builder for the open flags — Rust's fs::OpenOptions.
OpenMode's five variants are the common combinations, and they cannot
express the rest: there is no read+append, no create-without-truncate, no
read+write+create. Rather than grow that enum combinatorially, this builds
the flag set directly.
A plain value struct with functional setters, so a chain builds a new value at each step and nothing is shared or allocated:
f := io.await(
File.open_opts(path, OpenOptions.new().read(true).append(true).create(true), io),
{ io, exn }
);
to_flags() resolves the POSIX flag set, and REJECTS the combinations
POSIX would silently misinterpret rather than passing them through.
Fields
| Name | Type | Description |
|---|---|---|
_read | bool | |
_write | bool | |
_append | bool | |
_truncate | bool | |
_create | bool | |
_create_new | bool |
Methods
new : (OpenOptions) fn() -> OpenOptionsAll flags off. At least one of read/write/append must be set
before to_flags() will accept it.
Returns: OpenOptions
read : (OpenOptions) fn(self : OpenOptions, on : bool) -> OpenOptionsRead an entire file into a byte list (ArrayList(u8)).
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions | |
on | bool |
Returns: OpenOptions
write : (OpenOptions) fn(self : OpenOptions, on : bool) -> OpenOptionsWrite bytes to a file, creating or truncating it.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions | |
on | bool |
Returns: OpenOptions
append : (OpenOptions) fn(self : OpenOptions, on : bool) -> OpenOptionsEvery write goes to the END of the file regardless of the seek position
(O_APPEND). Grants write access on its own.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions | |
on | bool |
Returns: OpenOptions
truncate : (OpenOptions) fn(self : OpenOptions, on : bool) -> OpenOptionsTruncate the file to zero length on open. Needs write access.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions | |
on | bool |
Returns: OpenOptions
create : (OpenOptions) fn(self : OpenOptions, on : bool) -> OpenOptionsCreate the file if it does not exist. Needs write or append access.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions | |
on | bool |
Returns: OpenOptions
create_new : (OpenOptions) fn(self : OpenOptions, on : bool) -> OpenOptionsCreate the file, failing if it ALREADY exists. Implies create, and the
check-and-create is atomic (O_EXCL), which is what makes it usable as a
lock.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions | |
on | bool |
Returns: OpenOptions
creates : (OpenOptions) fn(self : OpenOptions) -> boolTrue when this combination can create the file, so the caller knows whether the permission argument is consulted.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions |
Returns: bool
to_flags : (OpenOptions) fn(self : OpenOptions) -> Result(i32, String)The POSIX flag set, or .Err naming the contradiction.
The rejected combinations are the ones POSIX does NOT diagnose — it quietly ignores whichever flag does not apply, handing back a file that behaves differently from what was asked:
- nothing to do: no
read,writeorappend. truncatewithout write access:O_TRUNCwithO_RDONLYis undefined by POSIX and a no-op on Linux.truncatewithappend: contradictory — append means every write goes to the end, truncate means the end is zero.createwithout write access: there is nothing to create the file for.
Parameters
| Name | Type | Notes |
|---|---|---|
self | OpenOptions |
POSIX file permission bits.
Fields
| Name | Type | Description |
|---|---|---|
mode | u32 |
Methods
default : (FilePermission) fn() -> FilePermission0o644 — rw-r--r--. The usual mode for a data file: writable by its
owner, readable by everyone. This is what File.open passes when the
mode creates a file and the caller named no permission; the process
umask still clears bits from it, so a umask of 0o022 is what makes
this the effective default rather than the request.
Returns: FilePermission
executable : (FilePermission) fn() -> FilePermission0o755 — rwxr-xr-x. default plus the execute bit for everyone:
scripts and binaries. Note the execute bit is only meaningful on POSIX;
Windows decides executability from the file extension.
Returns: FilePermission
readonly : (FilePermission) fn() -> FilePermission0o444 — r--r--r--. Readable by everyone, writable by nobody, which
is the state Metadata.is_readonly reports true for. It does not stop
root, and it does not stop the file's owner from chmod-ing it back.
Returns: FilePermission
private : (FilePermission) fn() -> FilePermission0o600 — rw-------. Owner only: the mode to use for anything holding
a credential, and the mode mkstemp (so TempFile) already creates
with. Unlike the other three this is not weakened by a typical umask,
because no group or other bit is being asked for.
Returns: FilePermission
Reference point for file seek operations.
Variants
| Variant | Fields | Description |
|---|---|---|
Start | Seek from the beginning of the file. | |
Current | Seek relative to the current position. | |
End | Seek relative to the end of the file. |
Functions
Check if a path exists, PROPAGATING errors (std::fs::try_exists):
.false only for a definitive not-there answer (ENOENT/ENOTDIR); a
permission failure or any other error THROWS instead of masquerading as
absence — the honest form exists cannot give
(plans/archive/STD_API_AUDIT.md §7 P0).
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
path | Path | Get the path this file was opened with. | |
io | Io |
Returns: Impl(Future(bool, IoExn))
Set a path's POSIX permission bits.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
path | Path | Get the path this file was opened with. | |
perm | FilePermission | ||
io | Io |
Returns: Impl(Future(unit, IoExn))
set_permissions (str path variant).
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
path | str | Get the path this file was opened with. | |
perm | FilePermission | ||
io | Io |
Returns: Impl(Future(unit, IoExn))