Hello Rustaceans!
Are you tired of handling multiple environment variables in a messy way? Let me show you how to load .env
files into typesafe structs in Rust, making your code cleaner and more maintainable.
The Traditional Approach
Initially, I used the dotenvy crate for loading .env
files like this:
use std::env;
fn main() {
dotenvy::dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
println!("DATABASE_URL {:?}", db_url);
}
This method is straightforward but quickly becomes unmanageable with more environment variables.
Learning from TypeScript
As a former TypeScript user, I appreciated how the zod
library ensured that all required environment variables were present:
const envSchema = z.object({
JOB_ID: z.string().min(1),
CLOUDFLARE_R2_ACCOUNT_ID: z.string().min(1),
CLOUDFLARE_R2_ACCESS_KEY_ID: z.string().min(1),
CLOUDFLARE_R2_SECRET_ACCESS_KEY: z.string().min(1),
});
const env = envSchema.parse(process.env);
This approach kept things organized and error-free. So, I wondered, can we achieve something similar in Rust?
The Rust Solution: envy
My search led me to the envy crate, which does exactly what I needed in Rust:
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Config {
database_url: String,
}
fn main() {
dotenvy::dotenv().ok();
let config = envy::from_env::<Config>().unwrap();
println!("DATABASE_URL {:?}", config.database_url);
}
With envy
, you can easily map environment variables to a typesafe struct.
Advanced Features and Defaults
envy
goes beyond basic deserialization. It supports Option
types, Vecs
, and more. You can even set default values using serde's attributes:
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Config {
database_url: String,
#[serde(default = "default_min_block_confirmations")]
min_block_confirmations: u8,
}
fn default_min_block_confirmations() -> u8 {
100
}
fn main() {
dotenvy::dotenv().ok();
let config = envy::from_env::<Config>().unwrap();
println!("DATABASE_URL {:?}", config.database_url);
println!("MIN_BLOCK_CONFIRMATIONS {:?}", config.min_block_confirmations);
}
Prefixing Environment Variables
For those who prefer prefixed environment variables, envy
has got you covered:
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Config {
database_url: String,
#[serde(default = "default_min_block_confirmations")]
min_block_confirmations: u8,
}
fn main() {
dotenvy::dotenv().ok();
let config = envy::prefixed("APP_").from_env::<Config>().unwrap();
println!("APP_DATABASE_URL {:?}", config.database_url);
println!("APP_MIN_BLOCK_CONFIRMATIONS {:?}", config.min_block_confirmations);
}
Conclusion
There you have it! Loading .env
files into typesafe structs in Rust is simple and efficient with envy
. This approach keeps your code neat and your variables organized. Give it a try, and you'll see how it can streamline your Rust projects.
Happy coding, and keep Rust-ing!