Chapter 19 11 min read

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:

output
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
  • (A-Z, a-z, _) through following name-continuation characters (those plus 0-9).

  • ${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
  • 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);
}
output
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 the
  • path/list separators you pair with env_get("PATH").

  • ioeprintln and the rest of stdin/stdout/stderr I/O used in the error
  • paths above.

  • cli — higher-level argument parsing built on env_args*.