std.env
std.env exposes immutable process-start inputs for command-line tools: arguments and environment variables.
argsreturns an immutable, allocation-free view of process-start arguments.variablesreturns an immutable, allocation-free view of one process-start environment snapshot.- Lossless bytes are the portable boundary; strict UTF-8 text is checked explicitly.
- Safe portable code cannot observe or mutate the live process-global environment.
Migration status: RFC 0021 fixes the target
Args/Argview documented below. RFC 0022 removes safe portable live-CWD observation and mutation, including the currentcwdandchdirsource experiments. RFC 0024 replaces the current liveget/set/removeand allocatingitemsexperiments with the immutable environment model documented below. The current source inventory remains transitional until those convergence slices are implemented.
Arguments
import std.bytes;
import std.env;
fn main() {
let arguments = env.args();
for argument in arguments {
let raw: bytes.Bytes = argument.bytes();
_ = raw.length;
}
}args() -> Args returns an opaque immutable view of the process-start argument vector. The call is O(1), infallible, and performs no allocation or host call. Repeated calls observe the same ordered arguments. The readonly Args.length field is the preserved O(1) argument count and may be zero; get(index) -> Arg? returns null outside the vector rather than trapping, and ordinary iteration yields arguments in host order.
Entry-point functions do not take an argv parameter. Use env.args() from the ordinary parameterless fn main() when command-line arguments are needed.
Args and Arg are opaque non-default Copy + Send + Sync handles. Args exposes the concrete structural iter(self) -> ^ArgsIter; it implements no source interface. ArgsIter is a stateful affine, non-Copy, non-Clone cursor implementing Iterator<Arg>. None has a public constructor or mutation surface. Their byte payloads live in one immutable process-start record and are not copied when an argument or source-view handle is copied.
An argument is lossless bytes first, checked text second:
import std.env;
fn main() {
for argument in env.args() {
let raw = argument.bytes();
let text = try argument.text();
_ = raw.length;
_ = text.size;
}
}Arg.bytes() returns a region-tied std.bytes.Bytes view over the target's lossless canonical representation. Arg.text() allocates nothing and returns a borrowed str only after checking strict UTF-8 and the absence of NUL; otherwise it reports std.unicode.utf8.UnicodeError.Invalid.
POSIX borrows initial argv bytes directly. Windows losslessly materializes UTF-16 arguments as WTF-8 once before first user entry; unpaired surrogates remain available through bytes() and fail text(). WASI imports its CLI argument value once before first user entry; U+0000 remains visible as a zero byte and fails text(). Windows/WASI preparation is included only when env.args is semantically reachable. Failure prevents process/component start before user entry; callers never observe a partial vector.
The vector may be empty. When argument zero is present it is an untrusted host value, not a guaranteed executable name, path, canonical path, or existing file. The API performs no shell parsing, glob expansion, path lookup, or normalization. Unsafe native mutation of POSIX argv or private backing is outside the safe contract.
There is no args_alloc helper. Code that needs an independent text value performs the checked conversion and ordinary explicit clone:
let argument = env.args().get(1);
if argument != null {
let owned: ^str = try (try argument.text()).clone();
keep(<-owned);
}Environment Snapshot
The accepted target captures one private immutable environment record before the first user entry and exposes only views over that record:
import std.env;
fn main() {
const variables = env.variables();
for variable in variables {
const name = variable.name();
const value = variable.value();
consume(name.bytes(), value.bytes());
}
}variables() -> Variables is infallible, O(1), allocation-free, and performs no host call. The readonly Variables.length field is the preserved entry count, get(index) -> Variable? returns null outside the vector, and ordinary iteration follows host order. Duplicate names are retained rather than hidden behind map semantics.
Variables, Variable, Name, and Value are opaque non-default Copy + Send + Sync handles. Variables exposes the concrete structural iter(self) -> ^VariablesIter and implements no source interface. VariablesIter is a stateful affine, non-Copy, non-Clone cursor implementing Iterator<Variable>. A snapshot-view handle copy does not copy its byte payload. There are no public constructors for snapshot handles and no mutation.
Variable.name() and Variable.value() return views into the same immutable record. Name.bytes() and Value.bytes() expose region-tied std.bytes.Bytes without allocation. Their text() methods return a borrowed str only after strict UTF-8 validation; invalid text reports std.unicode.utf8.UnicodeError.Invalid. An empty value is valid. A name is non-empty and contains neither NUL nor =; a value contains no NUL.
The target-canonical representations are:
- POSIX preserves arbitrary non-NUL bytes.
- Windows losslessly represents UTF-16 as WTF-8; unpaired surrogates remain available through
bytes()and failtext(). - WASI preserves its strict UTF-8 strings.
There is no implicit conversion from Name or Value to str, no Unicode or case normalization, and no mutable byte access. Code that needs independent storage uses ordinary explicit cloning:
const variable = env.variables().get(0);
if variable != null {
let name: ^env.Name = try variable.name().clone();
let value: ^env.Value = try variable.value().clone();
keep(<-name, <-value);
}Owning names and values can also be constructed explicitly from str or bytes.Bytes for child-process configuration. Construction validates the target representation and reports EnvironmentError.InvalidName or EnvironmentError.InvalidValue; allocation failure remains AllocError:
let name: ^env.Name = try env.Name("MODE");
let value: ^env.Value = try env.Value("test");No implicit conversion or sentinel bypasses those constructors.
Snapshot preparation
The runtime validates and materializes the complete record exactly once before the first user entry whenever env.variables or child process.Environment.Start is semantically reachable. It does not initialize the record lazily on the first call. Preparation failure, overflow, or OOM prevents user entry; no caller can observe a partial snapshot.
POSIX copies initial envp rather than borrowing live environ. Windows copies the initial UTF-16 block and excludes the special =C:-style per-drive working directory entries from portable std.env. WASI imports its environment value once. Any other malformed host entry prevents user entry. When neither snapshot observation nor .Start child configuration is reachable, the linker may omit the preparation path completely.
Unsafe target FFI may mutate a native process environment, but such mutation is outside the safe contract and never changes this snapshot.
No Current-Directory API
Portable safe Zynx does not expose a live process-global current directory. std.env has no cwd, chdir, with_cwd, global-lock, or thread-local-CWD contract. A lexical guard cannot control relative path operations performed by runtime workers, FFI, native libraries, or foreign threads that share the same OS process state.
Filesystem work uses explicitly anchored directory capabilities under the accepted RFC 0023 target contract. Application entry code declares an ordinary root-private @host let name: fs.Dir in _;; an RFC 0068 launch grant may bind that exact name to an explicit path or to the explicit process_start source. No source identifier, including start, has special meaning. Libraries receive a borrowed fs.Dir instead of consulting process-global state. There is no fs.open_root or named-root registry. The current text-only and process-global source helpers are not a compatibility layer for that API. Code that intentionally owns the complete native-process integration can declare native chdir through an explicit target-specific unsafe C FFI boundary.
Ownership, Allocation, Errors, And Unspecified Behavior
args() and variables() return immutable process-start views and allocate nothing per call. Their Copy handles share process-lifetime immutable backing; explicit clone() and owning Name/Value construction allocate. There is no safe live-environment or live-CWD operation in this module.
Invalid owned names or values are reported as the corresponding EnvironmentError case. DuplicateName is reserved for constructing a child-process replacement collection. Allocation failure is reported as AllocError.
Argument order and target-canonical bytes are preserved. Text validity is an explicit checked edge, and argument zero has no path or trust semantics. Environment ordering and duplicate names supplied by the host are preserved. Native live-environment mutation is outside the safe contract and cannot alter the snapshot. Platform-native APIs remain available only through an explicit target-specific unsafe boundary.
Related Modules
Use std.process for subprocess options and exit status. Use std.fs for filesystem operations after choosing an explicit path or directory anchor under the accepted filesystem contract.
API
| Symbol | Signature | Notes |
|---|---|---|
args | args() -> Args | Returns the immutable process-start argument view without allocation. |
Args | opaque struct: Copy + Send + Sync | Readonly length, bounds-checked lookup, and concrete structural iter(self) -> ^ArgsIter; no source-interface conformance. |
Arg | opaque struct: Copy + Send + Sync | Lossless bytes() plus checked borrowed text(). |
ArgsIter | opaque affine struct: Send + Sync + Iterator<Arg> | Package-constructed, non-Copy, non-Clone, allocation-free cursor yielding ^Arg? with sticky exhaustion. |
variables | target variables() -> Variables | Returns the immutable process-start environment view without allocation. |
Variables | target opaque struct: Copy + Send + Sync | Readonly preserved length, bounds-checked indexed lookup, and concrete structural iter(self) -> ^VariablesIter; no source-interface conformance. |
Variable | target opaque struct: Copy + Send + Sync | One snapshot name/value pair. |
VariablesIter | target opaque affine struct: Send + Sync + Iterator<Variable> | Package-constructed, non-Copy, non-Clone, allocation-free cursor over preserved host entries with sticky exhaustion. |
Name | target opaque struct: Copy + Send + Sync / owner ^Name | Lossless bytes(), checked borrowed text(), explicit clone(), and checked owning constructors. |
Value | target opaque struct: Copy + Send + Sync / owner ^Value | Lossless bytes(), checked borrowed text(), explicit clone(), and checked owning constructors. |
EnvironmentError | target error EnvironmentError { InvalidName, InvalidValue, DuplicateName } | Invalid owned environment data or duplicate child replacement name. |
Status
This page documents the accepted target contract. The current source still exports transitional Var, get, items, set, and remove experiments; they are not compatibility aliases and are removed by the RFC 0024 migration. RFC 0062 fixes Args.length and Variables.length as readonly fields rather than methods. For the exact currently exported surface, see Standard Library Inventory.