fs

tape
import fs from "fs";
import io from "io";

fs.write_file("notes.txt", "first line\n") or { return 1; };
fs.append_file("notes.txt", "second line\n") or { return 1; };
let text: string = fs.read_file("notes.txt") or {
    io.eprintln("cannot read notes.txt: {err}");
    return 1;
};

fs works on the real file system. Paths are []const u8 in the platform’s syntax; / works as a separator everywhere, \ on Windows too. To build paths, use path: path.join(dir, "notes.txt"). Every failure is an io.IoError.

Whole files

FunctionDoes
read_file(path) -> string or io.IoErrorThe file’s bytes as a string
write_file(path, data) -> void or io.IoErrorCreate or replace the file
append_file(path, data) -> void or io.IoErrorCreate the file or add to its end
copy_file(src, dst) -> void or io.IoErrorCopy the bytes, replacing dst

Opening a file

For more control, open a File. Say what you want to do with it (Access) and what should happen if it does or doesn’t exist (Creation):

tape
var f: fs.File = fs.open("data.bin", fs.Access.ReadWrite, fs.Creation.OpenOrCreate) or { return 1; };
defer f.close();
f.seek(16, fs.Whence.Start) or { return 1; };
var buf: [4]u8;
let n: i64 = f.read(buf[0..4]) or { return 1; };
Access
Readread only
Writewrite only
ReadWriteboth
Appendwrite, always at the end
CreationIf the file existsIf it doesn’t
Existingopen itNotFound
OpenOrCreateopen it, keep its contentscreate it
CreateNewAlreadyExistscreate it
Truncateopen it and empty itcreate it

Any Creation other than Existing needs a writing access, whether or not the file exists: open(p, Access.Read, Creation.Truncate) is InvalidInput. Opening a directory is IsADirectory.

MethodDoes
f.read(buf) -> i64 or io.IoErrorRead up to buf.len bytes; 0 at the end
f.write(data) -> i64 or io.IoErrorWrite some of data at the position
f.seek(off, whence) -> i64 or io.IoErrorMove to off from Start, Current or End; returns the new position
f.metadata() -> Metadata or io.IoErrorSize, kind, modification time
f.reader() -> io.Reader, f.writer() -> io.WriterThe file as a stream (for write_all, BufWriter, read_line)
f.close()Close it; closing twice does nothing

A File doesn’t close itself: close every file you open, defer f.close(); is the easy way. After close the other methods return InvalidInput.

Streams make files and buffering work together:

tape
var f: fs.File = fs.open("big.txt", fs.Access.Write, fs.Creation.Truncate) or { return 1; };
var out: io.BufWriter = io.buf_writer(f.writer());
for (i in 0..100000) { out.write_all("row {i}\n") or { return 1; }; }
out.close() or { return 1; };
f.close();

Directories and paths

FunctionDoes
exists(path) -> boolIs there something at path (following links)?
metadata(path) -> Metadata or io.IoErrorWhat is there (following links)
mkdir(path)Create one directory; its parent must exist
mkdir_all(path)Create a directory and any missing parents; fine if it exists
remove(path)Remove a file, a link, or an empty directory
remove_all(path)Remove a directory and everything in it (never following links)
rename(src, dst)Move within a volume, replacing a file at dst
read_dir(path) -> DirIter or io.IoErrorThe entries of a directory
temp_dir() -> stringA directory for temporary files
tape
var it: fs.DirIter = fs.read_dir("assets") or { return 1; };
for (e in it) {
    if (e.kind == fs.FileKind.Dir) {
        io.println("{e.name}/");
    } else {
        io.println(e.name);
    }
}

read_dir yields a DirEntry { name: string; kind: FileKind; } for each entry except . and .., in no particular order. name is the entry’s name, not a path: path.join(dir, e.name) makes one. The listing closes itself after the last entry; if you stop early, call it.close().

temp_dir() is $TMPDIR, $TMP or $TEMP (the first one set), or /tmp.

Metadata

tape
pub struct Metadata { kind: FileKind; size: i64; modified_ns: i64; read_only: bool; }
pub enum FileKind : u8 { File = 0; Dir = 1; Symlink = 2; Other = 3; }

modified_ns is the last modification time in nanoseconds since 1970-01-01 UTC, as precise as the file system keeps it; time.from_unix_nanos turns it into a date. read_only means nobody may write the file. metadata follows symbolic links, so its kind is never Symlink; a DirEntry’s kind is the entry itself and can be.

Platforms

Windows, Linux and macOS support all of fs; names are UTF-8 everywhere (converted to UTF-16 on Windows). Windows paths longer than 260 characters are not supported yet. On Uusi (Leivos) an absolute path such as /system/fonts/x.ttf is a file of Leivos’s storaged: opening, reading, writing, seeking, metadata (kind, size and modification time), directories, listings and rename (which replaces a file atomically) work; cwd and chdir are Unsupported, and without storaged an absolute path is NotFound. Any other path is the kernel’s (a file of the boot bundle, read only).

Last modified: