Os
let platform (): String
Which platform this program is running on: "linux", "macos" or "windows". Compiled in, not detected at runtime; a binary cannot move between platforms, so there is nothing to detect.
let temp_dir (): Result[String, String]
A fresh, empty, private directory. The caller owns it and is responsible for Os.remove_tree on it.
let self_exe (): Result[String, String]
The path of the running executable, for a program that needs to re-invoke itself.
let make_dir (path: String): Result[Unit, String]
Creates one directory. The PARENT must already exist; this makes a single level, not a path.
Err when the parent is missing, when the name is taken by a file, or when permission is refused. A directory that already exists is also an Err, so a caller who does not care should check Os.exists first.
let rename_file (from: String) (to: String): Result[Unit, String]
Atomic within one filesystem, and an error rather than a silent copy across two. The point is that the destination is never a half-written file.
let remove_file (path: String): Result[Unit, String]
Deletes a file. Err when it is not there, when it is a directory, or when permission is refused — Os.remove_tree is the one for directories.
let remove_tree (path: String): Result[Unit, String]
Deletes a file or directory tree. A final symlink is removed even with trailing separators; links encountered in the tree are never followed. Ancestors of the requested path resolve normally when the starting directory is acquired; traversal then uses held directories. Concurrent changes or resource exhaustion can return Err after partial removal. Traversal is limited to 128 directory levels to bound native stack, enumeration storage, and open handles. Empty paths and final . or .. components are rejected.
let copy_tree (src: String) (dst: String): Result[Unit, String]
Copies the CONTENTS of src into dst, which must already exist and must not overlap src. Returns Err for identical or nested directories before changing dst. File modes are not preserved.
let exit_with (code: Int): Unit
Exit with a status code, printing nothing. panic_raw also exits non-zero but prints its message first, which is wrong when the report has already been written.
let env_var_bytes (name: String): Option[Bytes]
Reads an environment value as bytes, preserving non-UTF-8 data.
None when unset; a set but empty value is Some(Bytes.empty()). The returned buffer owns a copy of the value, independent of later environment changes. Values cannot contain NUL in the OS environment.
let env_var (name: String): Option[String]
Reads an environment value as UTF-8 text.
None when unset or not valid UTF-8. A set but empty value is Some(""). Use Os.env_var_bytes to distinguish invalid text from an unset variable and preserve its bytes. Validation shares the byte buffer without making another copy of the value.
let read_file (path: String): Result[String, String]
Reads a whole seekable file as text.
Err when the file is missing, unreadable, or not valid UTF-8. Use Os.read_bytes for a file that is not text, or that might not be. A failed or incomplete read returns Err, never a partial string.
let write_file (path: String) (contents: String): Result[Unit, String]
Writes text to a file, creating it or REPLACING what was there.
Writes an exclusive sibling temporary, checks writing and closing, then replaces the destination in one same-filesystem rename. Err leaves the old destination intact; temporary files are removed on failure. The parent directory must exist and be writable. Symlinks and special files are rejected; parent directories are followed. On Unix, new files are private (0600) and replacements preserve ordinary permission bits. Replacement changes file identity: hard links, ownership, ACLs and other metadata are not preserved. This is atomic publication, not a promise of durability after power loss; interruption may leave a temporary file.
let read_bytes (path: String): Result[Bytes, String]
Reads a whole seekable file as bytes. An empty file returns empty Bytes. Opening, sizing, temporary buffer allocation, incomplete reads, I/O errors, and close failures return Err, never a partial buffer.
let write_bytes (path: String) (data: Bytes): Result[Unit, String]
Writes bytes to a file, creating it or REPLACING what was there.
The Bytes counterpart to Os.write_file: use this for anything that is not text, and for text you have already encoded.
let append_bytes (path: String) (data: Bytes): Result[Unit, String]
Creates the file when it does not exist, the way >> does.
enum Mode
How to open. A closed set, passed as small integers across the extern boundary; an extern taking a mode STRING would let a caller hand fopen anything at all.
enum Seek
Where a seek measures from.
let open (path: String) (mode: Mode): Result[File, String]
Write truncates an existing file; Append does not. Both create one that is not there, and Read does not.
struct ProcessResult
What a finished process left behind.
exit_code is the child's own status: a non-zero one is an ordinary result, not an error. Failing to START the process is the Err. What a finished process left behind.
exit_code is the child's own status: a non-zero one is an ordinary result, not an error. Failing to START the process is the Err.
let run_process (program: String) (args: Array[String]): Result[ProcessResult, String]
A non-zero exit code is an ordinary Ok. Err means the process could not be started at all.
let cwd (): Result[String, String]
The process's current working directory, as an absolute path.
let chdir (path: String): Result[Unit, String]
Changes the process's working directory.
Affects the WHOLE process, including every relative path used afterwards and every child it spawns — so a library that changes it and does not change it back has altered something its caller never agreed to.
let home_dir (): Option[String]
$HOME, or %USERPROFILE% on Windows. None when unset or not valid UTF-8 -- no guessing from a username, because a guess that is usually right is the worst kind of wrong for a path a program is about to write to.
let read_stdin_line (): Result[Option[String], String]
None is END OF STREAM and Some("") is a blank line, and keeping those apart is the whole reason these exist. The shims the language server uses return "" for both, so a filter reading until end of input would stop at the first blank line, which is wrong in a way that only shows up on real data.
The line arrives WITHOUT its newline, and a final line with no trailing newline is an ordinary line rather than something to drop.
let read_stdin (max_len: Int): Result[Bytes, String]
Up to max bytes, as Bytes. Stdin carries whatever was piped into it, which is not always text. With a positive maximum, an EMPTY result is end of stream; a SHORT one is data, because a pipe hands over what it has rather than waiting to fill a buffer.
A nonpositive max_len returns empty bytes immediately, without consuming input or checking for end of stream.
The bytes are copied into a Plum value before this returns, so the result stays valid across later reads; the shim's own buffer is reused and would not.
let write_stderr (s: String): Unit
Writes to standard error and flushes. NOT println, which is stdout, since a diagnostic that lands in the same stream as the program's output cannot be separated from it by whoever is reading. No trailing newline is added; say so if you want one.
enum Input
--- Reading with a deadline (issue #7) ---
Three outcomes, and Option has already spent itself on two of them: read_stdin_line uses None for end of stream, which is the distinction the whole function exists for. A timed read has to say "a line", "the input ended" or "nothing arrived in time", so it says so in a type with three names rather than nesting two Options and asking the reader to remember which is which.
let read_stdin_line_timeout (d: Time.Duration): Result[Input[String], String]
The timeout bounds the WHOLE CALL, not just the wait for the first byte, so a slow writer cannot leave a caller blocked mid-line past its deadline. Bytes already read are kept, so TimedOut is never data loss: calling again resumes where this stopped.
let read_stdin_timeout (max_len: Int) (d: Time.Duration): Result[Input[Bytes], String]
Up to max_len bytes, with the same three outcomes. A SHORT read is Got, not TimedOut: a pipe handing over what it has is data arriving, not a deadline passing. A nonpositive max_len returns Got with empty bytes immediately, even at end of stream or with a zero timeout, without consuming input.
struct Metadata
No exists field: a path that is not there is an Err, so a Metadata in hand always describes something real. That is the contract is_directory already set, and having one of these answer exists: false while the other errored would be two answers to one question.
let exists (path: String): Result[Bool, String]
Three-way underneath, and the third case is why this returns a Result rather than a Bool: "does this exist" and "can I see whether this exists" are different questions. A permission error is an Err, not a silent Ok(false).
This is the ONE function here where a missing path is Ok, and deliberately so; it is the function whose whole job is to say so.
let stat (path: String): Result[Metadata, String]
A missing path is an Err, matching is_directory.
let file_size (path: String): Result[Int, String]
Bytes. Meaningful for a regular file; for a directory it is whatever the filesystem records for the directory itself, which is not the size of its contents.
let mtime (path: String): Result[Int, String]
Seconds since the Unix epoch, the same unit as Time.now(), so the two can be compared without a conversion.
let list_dir (path: String): Result[Array[String], String]
Lists a directory's entries by NAME, without . or .., in whatever order the filesystem gives.
Names, not paths: join them onto the directory with Path.join to get something you can open.
let is_directory (path: String): Result[Bool, String]
Whether the path is a directory. Ok(false) for a file, and for something that is not there at all — use Os.exists to tell those apart.