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