70 %

Toast is in Beta!Learn more →

Chris Biscardi

Dealing with environment variables in Rust

If you're unfamiliar with environment variables, read the intro post.

Getting a single env var

The Rust standard library provides std::env::var for getting environment variables. Notably this function will panic in certain cases even though it returns a Result.

rust
use std::env;
fn main() {
match env::var("MYAPP_FLAG") {
Ok(val) => println!("{}: {:?}", key, val),
Err(e) => println!("couldn't interpret {}: {}", key, e),
}
}

While std::env::var guarantees that a resulting value will be valid unicode, there is a variant that doesn't have the same restriction: std::env::var_os.

rust
use std::env;
fn main() {
match env::var_os("MYAPP_FLAG") {
Ok(val) => println!("{}: {:?}", key, val),
Err(e) => println!("couldn't interpret {}: {}", key, e),
}
}

Getting all the env vars

std::env::vars and std::env::vars_os will yield an iterator (which we use here to count) that we can use to print out all of the values using inspect (or a for loop, or any other way).

rust
use std::env;
fn main() {
let count = env::vars()
.inspect(|(key, value)| println!("{}: {}", key, value))
.count();
println!("{} env vars", count)
}

Envy

The previous approaches are nice to have in the standard library, but they don't do everything that we typically need when dealing with env vars. After all, we use env vars for a purpose: to configure our applications. This is where third party crates like envy come in.

rust
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Config {
foo: u16,
bar: bool,
baz: String,
boom: Option<u64>
}
fn main() {
match envy::from_env::<Config>() {
Ok(config) => println!("{:#?}", config),
Err(error) => panic!("{:#?}", error)
}
}

Envy (through using Serde) allows us to define structs that represent the configuration our application is expecting from the environment, including optional and required values, and then parse them into a struct for use in the rest of our application. If the configuration isn't suitable (there are some missing required values maybe) we get the opportunity to stop our program early and notify the user.

Using a crate like envy is preferable if we want to set default values in an idiomatic Rust way (using the Default trait) (or serde attributes) or just have a lot of environment configuration to deal with.

Etc

There are also other libraries that try to mimic the behavior from other ecosystems like dotenv or viperus. I prefer to use a cross-tool solution like direnv and envy instead of a framework specific loader like dotenv and envy.