std/process
Command execution and child process management.
from "std/process" import { Process, run, capture }Types
Process
struct Process {
pid: i32,
}Handle to a spawned child process.
Functions
run
fn run(command: &string): Result<i32>Execute a shell command and wait for it to finish. Returns the exit code.
Process.spawn
fn Process.spawn(command: &string): Result<Process>Start a command in the background without waiting. Returns a Process handle.
p.wait
fn wait(self: &Process): Result<i32>Block until the process exits. Returns the exit code.
capture
fn capture(command: &string): Result<string>Execute a command and return its stdout as a string.
p.signal
fn signal(self: &Process, sig: i32): Result<i32>Send a POSIX signal to the process. Returns 0 on success.
Command
Command builds a child process: program, arguments, working directory, environment, and where each standard stream goes. Each method takes the builder and hands it back, so calls chain; spawn borrows, so one Command can start several children.
from "std/process" import { Command, Stdio }
let child = Command.new("git")
.arg("status")
.dir(repoPath)
.env("GIT_PAGER", "cat")
.stderr(Stdio.Pipe)
.spawn()!| Method | Effect |
|---|---|
Command.new(program) | PATH-searched program, no arguments |
.arg(value) / .args(values) | append arguments — passed verbatim, no shell |
.dir(path) | run the child in path |
.env(name, value) | set a variable for the child only |
.envRemove(name) | drop an inherited variable |
.envClear() | start from an empty environment instead of inheriting |
.stdin(mode) / .stdout(mode) / .stderr(mode) | point one stream somewhere |
.spawn() | start the child, returning Result<Child> |
Defaults are stdin and stdout on pipes with stderr merged into stdout — the same shape Child.spawn(program, args, true) has always had. When stderr is not merged, drain both streams or the child can deadlock filling an unread pipe.
Stdio
enum Stdio {
Pipe, // the parent holds the other end
Inherit, // the parent's own stream
Null, // /dev/null, or NUL on Windows
Merge, // stderr only: wherever stdout went
Read(string), // stdin only: read this file
Write(string), // stdout/stderr only: create or truncate this file
Append(string), // stdout/stderr only: create or append to this file
}The last four are direction-specific. Pointing one at the wrong stream is a spawn error naming the stream, and no child is created — a redirection that cannot be honoured never silently lands on your terminal instead.
Reading a stream that is not a pipe is a contract violation: writeStdin, readStdout and stdout() all require their fd, checked in debug builds.
Command.env vs Env.set
Command.env changes one child's environment. Env.set (in std/env) changes this process's environment, which every later child inherits. Prefer Command.env — it has no thread-safety caveat and no effect on anything but the child you are starting.
Example
from "std/process" import { Command, Process, Stdio, run, capture }
fn main(): i32 {
// Run and get exit code
let code = run("echo hello")!
// Capture output
let output = capture("uname -s")!
print(output)
// Spawn and wait
let proc = Process.spawn("sleep 1")!
let exitCode = proc.wait()!
// A child with its own cwd, environment and log file
var child = Command.new("make")
.arg("-j4")
.dir("build")
.env("CC", "clang")
.stdout(Stdio.Write("build.log"))
.stderr(Stdio.Merge)
.spawn()!
let status = child.wait()!
child.close()
return 0
}