2024-01-07
*post*
================================================================================

Load .env in Rust

================================================================================

You can find the source code for this post right over  here

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.

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.

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?

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.

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);
}

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);
}

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!

================================================================================

TAGS

*post-tags*

================================================================================

LINKS

*post-links*