process.env.dev

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:

rust
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:

rust
# 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:

rust
// 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:

rust
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_var is 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::var fails on non-UTF-8 values. If you are working with paths or values that may contain non-Unicode bytes, use env::var_os instead.
  • When cross-compiling, remember that environment variables are read at runtime, not compile time. Theenv! macro reads variables at compile time and is a different mechanism entirely.