Environment Variables in Rust
Using std::env and dotenvy
Reading Environment Variables
Rust provides environment variable access through the std::env module. The primary functions are env::var and env::var_os:
use std::env;
fn main() {
// Returns Result<String, VarError>
match env::var("DATABASE_URL") {
Ok(url) => println!("Database: {url}"),
Err(err) => eprintln!("DATABASE_URL not set: {err}"),
}
// Unwrap with a default
let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
let port: u16 = port.parse().expect("PORT must be a number");
// Check all environment variables
for (key, value) in env::vars() {
println!("{key}={value}");
}
}env::var returns a Result<String, VarError>. The error type distinguishes between "not present" (VarError::NotPresent) and "not valid Unicode" (VarError::NotUnicode). Use env::var_os if you need to handle non-UTF-8 values, which returns Option<OsString>.
Setting Environment Variables
Set variables in the shell before running your binary, or use env::set_var at runtime:
# Shell
export DATABASE_URL=postgres://localhost/mydb
cargo run
// In Rust (unsafe in multi-threaded contexts since Rust 1.66)
unsafe {
env::set_var("MY_VAR", "value");
env::remove_var("MY_VAR");
}Note that env::set_var and env::remove_var are marked as unsafe in Rust 1.66+ when used in multi-threaded programs, because modifying the environment is not thread-safe on most platforms. Prefer reading all variables at startup and storing them in a configuration struct.
Popular Libraries
dotenvy (a maintained fork of the original dotenv crate) loads .env files:
// Cargo.toml: dotenvy = "0.15"
fn main() {
dotenvy::dotenv().ok(); // loads .env, ignores if missing
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
}envy deserializes environment variables into a struct using serde:
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
database_url: String,
#[serde(default = "default_port")]
port: u16,
}
fn default_port() -> u16 { 3000 }
fn main() {
dotenvy::dotenv().ok();
let config: Config = envy::from_env().expect("Failed to parse config");
}config is a more comprehensive configuration library that supports environment variables, TOML, JSON, and YAML sources with layered merging.
Common Gotchas
env::set_varis unsafe in multi-threaded contexts. Read all configuration at startup inmain()before spawning threads or starting the async runtime.- All environment variable values are strings. Parse them explicitly with
.parse()and handle the error. Using.unwrap()in production is risky — prefer.expect()with a descriptive message or proper error handling. env::varfails on non-UTF-8 values. If you are working with paths or values that may contain non-Unicode bytes, useenv::var_osinstead.- When cross-compiling, remember that environment variables are read at runtime, not compile time. The
env!macro reads variables at compile time and is a different mechanism entirely.