Environment and Process Management

Environment Variables

Environment variables are key-value pairs inherited by a process from its parent environment, commonly used to configure runtime behavior, credentials, locale settings, and search paths. Rust provides both runtime inspection via std::env (var, var_os, vars, vars_os) and compile-time inspection via the env! and option_env! macros.

Runtime Inspection: var() vs var_os()

std::env::var(key) fetches an environment variable and validates that its contents are UTF-8, returning Result<String, VarError>.

std::env::var("KEY")
  |
  +-- Ok(String)                        Variable exists and is valid UTF-8
  +-- Err(VarError::NotPresent)         Variable is not set in the environment
  +-- Err(VarError::NotUnicode(OsString)) Variable exists but contains non-UTF-8 bytes

When an environment variable holds a file system path or arbitrary OS bytes, std::env::var_os(key) returns Option<OsString> without failing on non-Unicode data.

use std::env;
use std::path::PathBuf;

fn resolve_port() -> u16 {
    match env::var("APP_PORT") {
        Ok(val) => val.parse::<u16>().unwrap_or(8080),
        Err(env::VarError::NotPresent) => 8080,
        Err(env::VarError::NotUnicode(_)) => {
            eprintln!("Warning: APP_PORT contains invalid UTF-8; defaulting to 8080");
            8080
        }
    }
}

fn resolve_cache_dir() -> PathBuf {
    env::var_os("APP_CACHE_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(env::temp_dir)
}

fn main() {
    println!("Resolved port: {}", resolve_port());
    println!("Cache dir exists: {}", resolve_cache_dir().exists());
}
Resolved port: 8080
Cache dir exists: true

Iterating Over All Variables

env::vars() yields (String, String) pairs (panicking if any key or value is not valid UTF-8), whereas env::vars_os() yields (OsString, OsString) pairs safely across all encodings.

use std::env;

fn main() {
    let cargo_vars: Vec<(String, String)> = env::vars_os()
        .filter_map(|(k, v)| {
            let key = k.into_string().ok()?;
            let val = v.into_string().ok()?;
            key.starts_with("CARGO_PKG_").then_some((key, val))
        })
        .collect();

    println!("Found {} CARGO_PKG_* variables", cargo_vars.len());
}
Found 0 CARGO_PKG_* variables

Mutating Environment Variables (unsafe in Rust 2024)

In the Rust 2024 edition, env::set_var and env::remove_var are unsafe functions because modifying the process environment is not thread-safe on POSIX operating systems if any other thread is running. They should only be called in single-threaded initialization code (before spawning threads) and accompanied by a // SAFETY: comment.

use std::env;

fn main() {
    // SAFETY: Called in `main` while the process is strictly single-threaded;
    // no worker threads have been spawned yet.
    unsafe {
        env::set_var("SERVICE_ENV", "staging");
    }

    assert_eq!(env::var("SERVICE_ENV").as_deref(), Ok("staging"));

    // SAFETY: Still single-threaded.
    unsafe {
        env::remove_var("SERVICE_ENV");
    }

    assert_eq!(env::var("SERVICE_ENV"), Err(env::VarError::NotPresent));
}

Compile-Time vs Runtime Environment Lookup

Mechanism Evaluation Time Return Type Missing Variable Behavior
env!("KEY") Compile time &'static str Fails compilation with a compiler error
option_env!("KEY") Compile time Option<&'static str> Evaluates to None at compile time
std::env::var("KEY") Runtime Result<String, VarError> Returns Err(VarError::NotPresent)
std::env::var_os("KEY") Runtime Option<OsString> Returns None
fn main() {
    // Embedded into the binary by Cargo at compile time
    let pkg_version: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
    println!("Compiled package version: {:?}", pkg_version.is_some());
}
Compiled package version: true