Environment & CLI args
The stdlib::env module is Glide's window into the per-process environment: the command-line arguments the program was launched with, the environment variables it inherited, and the ability to read, mutate, enumerate, and expand them — plus an immediate process exit. Static host facts (OS name, arch, executable path, path separators) live in stdlib::os instead.
Import
import stdlib::env::*;
Public surface at a glance
| Item | Kind | Signature |
|---|---|---|
env_args_count |
fn | fn env_args_count() -> i32 |
env_args_at |
fn | fn env_args_at(i: i32) -> string |
env_args |
fn | fn env_args() -> *Vector<string> |
env_get |
fn | fn env_get(name: string) -> string |
env_has |
fn | fn env_has(name: string) -> bool |
env_set |
fn | fn env_set(name: string, value: string) -> ! |
env_unset |
fn | fn env_unset(name: string) -> ! |
env_all |
fn | fn env_all() -> *Vector<EnvKV> |
env_expand |
fn | fn env_expand(s: string) -> string |
env_exit |
fn | fn env_exit(code: i32) |
EnvKV |
struct | pub struct EnvKV { pub key: string, pub value: string } |
Every public function and the single public type are documented below.
CLI arguments
Argument indexing mirrors C's argc/argv: index 0 is the program path and user-supplied arguments start at 1.
| Function | Signature | Description |
|---|---|---|
env_args_count |
fn env_args_count() -> i32 |
Total number of CLI arguments, including the program path at index 0. |
env_args_at |
fn env_args_at(i: i32) -> string |
Argument at index i. Returns "" for out-of-range indices. |
env_args |
fn env_args() -> *Vector<string> |
All user arguments as a vector, skipping the program path at index 0. |
env_args_count / env_args_at
env_args_at is total-safe: any out-of-range index (negative, or >= env_args_count()) yields "" rather than trapping.
import stdlib::env::*;
fn main() -> i32 {
let n: i32 = env_args_count();
println!("argc =", n);
let prog: string = env_args_at(0); // program path
let first: string = env_args_at(1); // first user arg, "" if missing
println!("prog =", prog);
println!("first =", first);
return 0;
}
Invoked as ./app build --release:
argc = 3
prog = ./app
first = build
env_args
env_args returns a freshly allocated *Vector<string> containing only the user arguments (index 1 onward), which is usually what an argument parser wants.
import stdlib::env::*;
fn main() -> i32 {
let args: *Vector<string> = env_args();
for let i: i32 = 0; i < args.len(); i++ {
println!("arg", i, args.get(i));
}
return 0;
}
Argument parsing basics
env deliberately ships no flag parser — it hands you the raw argument vector and you decide the convention. The pattern below handles bare flags (-v / --verbose) and --key=value pairs by splitting on =:
import stdlib::env::*;
fn main() -> i32 {
let args: *Vector<string> = env_args();
let mut verbose: bool = false;
let mut out: string = "a.out";
for let i: i32 = 0; i < args.len(); i++ {
let a: string = args.get(i);
if a.eq("-v") || a.eq("--verbose") {
verbose = true;
} else if a.contains("=") {
let parts: *Vector<string> = a.split("=");
if parts.len() == 2 && parts.get(0).eq("--out") {
out = parts.get(1);
}
}
}
println!("verbose =", verbose);
println!("out =", out);
return 0;
}
Reading environment variables
| Function | Signature | Description |
|---|---|---|
env_get |
fn env_get(name: string) -> string |
Value of name, or "" when unset. |
env_has |
fn env_has(name: string) -> bool |
true only if name is set with a non-empty value. |
env_get
Returns "" for an unset variable. Because an empty value and an unset variable both read as "", use env_has when you need to tell them apart.
import stdlib::env::*;
fn main() -> i32 {
let path: string = env_get("PATH");
println!("PATH len =", path.len());
return 0;
}
env_has
import stdlib::env::*;
fn main() -> i32 {
if env_has("DEBUG") {
println!("debug on");
} else {
println!("debug off");
}
return 0;
}
Reading config with defaults
A common idiom is "read a variable, parse it, fall back to a default." Because string.try_parse_int() returns !i32, you can default the whole expression with ??. Wrap the lookup so that unset and unparseable both surface as an err, then collapse them to the default in one place:
import stdlib::env::*;
fn port_from_env() -> !i32 {
let raw: string = env_get("PORT");
if raw.eq("") { return err("PORT unset"); }
return raw.try_parse_int(); // err on non-numeric text
}
fn main() -> i32 {
// Default when unset OR unparseable, in one expression.
let port: i32 = port_from_env() ?? 8080;
let host: string = env_get("HOST");
if host.eq("") { host = "127.0.0.1"; } // string default
println!("listening on", host, port);
return 0;
}
Mutating environment variables
Both mutators return the bare result type ! (Result with no payload). Errors are rare and originate from the OS — typically an invalid name (e.g. one containing =) or a system limit. Propagate with ? in a !T function, or inspect .ok / .err.
| Function | Signature | Description |
|---|---|---|
env_set |
fn env_set(name: string, value: string) -> ! |
Set name to value, overwriting any existing value. Err if the OS rejects the call. |
env_unset |
fn env_unset(name: string) -> ! |
Remove name. Idempotent — unsetting a missing var is not an error. |
env_set is cross-platform: on Windows it uses _putenv_s, which updates both the Win32 environment block and the CRT table so a later env_get sees the new value; on POSIX it is setenv(name, value, 1). env_unset removes the variable from both stores.
env_set / env_unset
import stdlib::env::*;
fn main() -> !i32 {
env_set("LANG", "en_US.UTF-8")?;
let v: string = env_get("LANG");
println!("LANG =", v);
env_unset("LANG")?;
if env_has("LANG") {
println!("still set");
} else {
println!("unset ok");
}
return ok(0);
}
To handle the error without propagating, bind the result and read its fields. eprintln (from stdlib::io) writes to stderr:
import stdlib::env::*;
import stdlib::io::*; // eprintln
fn main() -> i32 {
let r: ! = env_set("GLIDE_DOC_DEMO", "1");
if !r.ok {
eprintln(r.err);
return 1;
}
println!("set:", env_get("GLIDE_DOC_DEMO")); // "1"
println!("has:", env_has("GLIDE_DOC_DEMO")); // true
let u: ! = env_unset("GLIDE_DOC_DEMO");
if !u.ok { eprintln(u.err); return 1; }
println!("has after unset:", env_has("GLIDE_DOC_DEMO")); // false
return 0;
}
Enumerating the environment
struct EnvKV
One decomposed environment entry, as returned by env_all().
pub struct EnvKV {
pub key: string,
pub value: string,
}
| Field | Type | Description |
|---|---|---|
key |
string |
Variable name (text left of the first =). |
value |
string |
Variable value (text right of the first =). |
env_all
| Function | Signature | Description |
|---|---|---|
env_all |
fn env_all() -> *Vector<EnvKV> |
Snapshot of the entire process environment, split into EnvKV pairs. |
The returned vector is a copy taken at call time. Malformed raw entries (those with no =, or an empty key) are skipped. Calling env_set / env_unset afterward does not update an already-returned vector — call env_all() again for a fresh snapshot.
import stdlib::env::*;
fn main() -> i32 {
let all: *Vector<EnvKV> = env_all();
println!("env count =", all.len());
for let i: i32 = 0; i < all.len(); i++ {
let kv: EnvKV = all.get(i);
println!(kv.key, "=", kv.value);
}
return 0;
}
Because env_has/env_get cannot distinguish FOO= (empty) from an unset FOO, env_all is the tool for an exact-presence check. Returning ?string lets the caller branch on some/none:
import stdlib::env::*;
fn lookup(key: string) -> ?string {
let all: *Vector<EnvKV> = env_all();
for let i: i32 = 0; i < all.len(); i++ {
let kv: EnvKV = all.get(i);
if kv.key.eq(key) { return some(kv.value); }
}
return none();
}
fn main() -> i32 {
let r: ?string = lookup("PATH");
match r {
some(v) => println!("PATH value length", v.len()),
none() => println!("PATH absent"),
}
return 0;
}
Variable expansion
env_expand
| Function | Signature | Description |
|---|---|---|
env_expand |
fn env_expand(s: string) -> string |
Expand $VAR, ${VAR}, and %VAR% references in s to their current env values. |
Expansion rules:
$VAR— bare POSIX form. The name runs from the first name-start character${VAR}— POSIX braces, ended by the matching}.%VAR%— Windows form, ended by the next%.- Unknown variables expand to the empty string.
- A
$followed by a non-name character, or a%with no closing%, is left
(A-Z, a-z, _) through following name-continuation characters (those plus 0-9).
untouched (preserved literally).
import stdlib::env::*;
fn main() -> !i32 {
env_set("DEMO_DIR", "/srv")?;
// $VAR and %VAR% can be written inline.
println!(env_expand("posix: $DEMO_DIR/logs"));
println!(env_expand("win: %DEMO_DIR%\\logs"));
// ${VAR} would collide with Glide's own string interpolation, so build
// the literal by concatenation instead of writing the braces inline.
let tmpl: string = "braced: ".concat("$").concat("{").concat("DEMO_DIR").concat("}/x");
println!(env_expand(tmpl));
// Unknown var -> empty; lone $ before a non-name char is preserved.
println!(env_expand("missing=[$NOPE_VAR] price=$5"));
return ok(0);
}
posix: /srv/logs
win: /srv\logs
braced: /srv/x
missing=[] price=$5
Process exit
env_exit
| Function | Signature | Description |
|---|---|---|
env_exit |
fn env_exit(code: i32) |
Terminate the process immediately with code. |
env_exit calls the C exit directly: it skips Glide-side cleanup, so defer blocks and auto-drops (let v* = ...) in enclosing stack frames will not run. Use it for hard, top-level termination after reporting an error; prefer returning a non-zero i32 from main when you want normal unwinding.
import stdlib::env::*;
import stdlib::io::*; // eprintln
fn main() -> i32 {
let mode: string = env_get("MODE");
if mode.eq("") {
eprintln("MODE is required");
env_exit(2); // skips defer/auto-drop cleanup
}
println!("mode =", mode);
return 0;
}
See also
os— static host facts: OS name, architecture, executable path, and theio—eprintlnand the rest of stdin/stdout/stderr I/O used in the errorcli— higher-level argument parsing built onenv_args*.
path/list separators you pair with env_get("PATH").
paths above.