Skip to content

Process

4 min read

system.process launches owned child processes on native and Node. Browser launch operations return ProcessError with code EUNSUPPORTED.

import system;
import system.process;

async fun main(): void {
    let options = ProcessOptions();
    options.env["APP_MODE"] = "test";
    options.timeout_ms = 5000;
    let output = Process.run("example-tool", ["--check"], options: options).await;
    switch (output) {
        Ok(value) => System.println(value.stdout),
        Err(error) => System.println(error.message()),
    }
}

The simple run(command, arguments) overload uses default options. run feeds options.input to stdin, closes it, drains stdout and stderr concurrently, and waits for exit. Argument boundaries preserve empty strings, Unicode, whitespace, and newlines through length-prefixed UTF-8 encoding. Commands launch directly without shell interpolation.

Options inherit the environment and working directory by default. Set env overrides, clear_env, cwd, and timeout_ms explicitly. Captured output defaults to an 8 MiB limit per stream; exceeding max_output_bytes returns ELIMIT. Input is buffered in the options snapshot. A child that rejects input can cause an I/O error.

A nonzero exit status is a successful transport result; run_checked turns it into EEXIT. which searches for an existing nondirectory path, without validating execute permissions.

Process functions

Every launching function accepts an optional trailing token: Option<CancellationToken>.

Function Returns Effect
Process.run(cmd, args) Future<Result<ProcessOutput, ProcessError>> Runs with default options and captures both outputs.
Process.run(cmd, args, options) Future<Result<ProcessOutput, ProcessError>> Runs with the given ProcessOptions.
Process.run_in(cmd, args, cwd) Future<Result<ProcessOutput, ProcessError>> Runs in the directory cwd.
Process.run_checked(cmd, args) Result<ProcessOutput, ProcessError> (async) Like run, but a nonzero exit returns EEXIT.
Process.spawn(cmd, args) Future<Result<ChildProcess, ProcessError>> Starts a child with streaming pipes.
Process.spawn(cmd, args, options) Future<Result<ChildProcess, ProcessError>> Starts a child with the given ProcessOptions.
Process.spawn_in(cmd, args, cwd) Future<Result<ChildProcess, ProcessError>> Starts a child in the directory cwd.
Process.which(cmd) Option<string> Finds a command path directly or on PATH; native and Node only.

Options

Field Default Effect
cwd "" Working directory; empty inherits the parent's directory.
env empty Map<string, string> Variables added on top of the inherited environment.
clear_env false When true, the child starts with only the env entries.
timeout_ms 0 Wall-clock deadline in milliseconds; zero means no deadline.
max_output_bytes 8388608 (8 MiB) Limit per captured stream for run; exceeding it returns ELIMIT.
input empty byte[] Bytes run writes to stdin before closing it.

Captured output

Property Type Meaning
exit_code int Exit status; negative values mean the process was ended by a signal.
stdout string Captured standard output, decoded as UTF-8.
stderr string Captured standard error, decoded as UTF-8.
success bool True when the exit status is zero.

Streaming pipes

let child = Process.spawn("example-tool", ["--stream"]).await?;
let stdout = child.stdout;
let stderr = child.stderr;
let stdin = child.stdin;
let written = stdin.write_async(Encoding.utf8_encode("input\n")).await?;
child.close_stdin().await?;
let line = stdout.read_line().await?;
let status = child.wait().await?;
child.close().await?;
Member Returns Effect
child.stdout / child.stderr ProcessReader Borrows an output pipe.
child.stdin ProcessWriter Borrows the input pipe.
child.close_stdin() Future<Result<Unit, ProcessError>> Closes stdin after pending writes, sending EOF to the child.
child.wait() Future<Result<int, ProcessError>> Waits for exit and returns the exit status.
child.close() Future<Result<Unit, ProcessError>> Terminates if needed, reaps the child, and releases it.
reader.read_async(max_bytes) Result<byte[], IoError> (async) Reads up to max_bytes; an empty result means EOF.
reader.read_line(max_bytes = 1048576) Future<Result<Option<string>, IoError>> Reads one line without its LF or CRLF; None at EOF.
writer.write_async(data) Result<int, IoError> (async) Writes some or all bytes and returns the count.
writer.flush_async() Result<Unit, IoError> (async) Checks the pipe is open; there is no buffer to flush.

stdin implements AsyncWriter; stdout and stderr implement AsyncReader. Reads and writes return partial progress and propagate failures. Streams.copy_async connects process pipes to files, HTTP bodies, and other async streams. Writes are not guaranteed to consume the entire buffer; retry the remaining bytes or use the copy helper.

Drain stdout and stderr concurrently when a command can fill both pipes. Waiting for process exit before draining can deadlock on output backpressure. A retained ProcessReader provides buffered read_line, with LF/CRLF handling, a line-size limit, and final unterminated lines.

wait leaves the handle and unread pipes available. close().await terminates unfinished work, reaps the direct child, and closes ownership idempotently. Concurrent closes share the outcome. A destructor requests nonthrowing cleanup; explicit close reports errors. Pipe wrappers retain their owning child.

Cancellable operations inherit the current structured task's token when omitted. Cancelling run or its deadline terminates and reaps the owned child before returning ECANCELLED or ETIMEOUT. Cancelling an individual pipe operation preserves completed side effects and leaves the child available for explicit closure. Spawned-child deadlines also terminate and reap the child. Descendant process trees are not managed as a group.

Error codes

ProcessError.code() and the IoError codes returned by pipe operations include:

Code Meaning
ESPAWN The command could not be started, for example because it was not found or is not executable.
EEXIT run_checked saw a nonzero exit status.
ETIMEOUT The timeout_ms deadline passed; the child was terminated.
ECANCELLED The operation's cancellation token was cancelled.
ELIMIT Captured output exceeded max_output_bytes, or a line exceeded the read_line limit.
EINVAL A request was malformed, such as an environment name containing =.
ECLOSED The child or the pipe was already closed.
EIO Reading from or writing to the child failed.
EUNSUPPORTED The host cannot launch processes, as in browsers.