DEV Community

Veerawat
Veerawat

Posted on

Load and Map Environment Variables in Rust with dotenvy, serde, and envy

Mapping environment variables into a Rust configuration struct
Environment variable management is an important part of configuring applications. In Rust, we can load variables from a .env file and map them into a struct for type-safe access using the crates dotenvy, serde, and envy.

Install Dependencies

First, add the dotenvy crate to your project:

cargo add dotenvy

Then check your Cargo.toml to confirm it’s installed.

dotenvy

Create the .env File

Create a .env file in the project root directory:

ENVIRONMENT="development"
APP_USER="test"
PORT=555
Enter fullscreen mode Exit fullscreen mode

folders

Reading Environment Variables Manually

Here’s how you can load variables from the .env file using dotenvy and read them manually:

use std::env;

fn main() {
    dotenvy::dotenv().ok(); // Load from .env

    let environment = env::var("ENVIRONMENT").expect("ENVIRONMENT not set");
    let app_user = env::var("APP_USER").expect("APP_USER not set");
    let port = env::var("PORT").expect("PORT not set");

    println!("ENVIRONMENT = {}", environment);
    println!("USER = {}", app_user);
    println!("PORT = {}", port);
}
Enter fullscreen mode Exit fullscreen mode

The expect calls here handle errors in a fail-fast style, ensuring the variables exist and are ready to use.

Mapping to a Struct with serde

Instead of manually extracting each value, you can declare a Rust struct and derive Deserialize from serde to handle the data:

cargo add serde --features derive

This enables the required derive feature. Then:

serde = {version="1.0.228", features = ["derive"]}
Enter fullscreen mode Exit fullscreen mode
use std::env;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Config {
    environment: String,
    app_user: String,
    port: u16,
}

fn main() {
    dotenvy::dotenv().ok(); // Load from .env

    let config = Config {
        environment: env::var("ENVIRONMENT").expect("ENVIRONMENT not set"),
        app_user: env::var("APP_USER").expect("APP_USER not set"),
        port: env::var("PORT").expect("PORT not set"),
    };

    println!("ENVIRONMENT: {}", config.environment);
    println!("APP_USER: {}", config.app_user);
    println!("PORT: {}", config.port);
}
Enter fullscreen mode Exit fullscreen mode

This method still manually maps values but benefits from typed fields thanks to serde.

Automatically Mapping with envy

To avoid mapping environment variables one by one, you can use the envy crate. It automatically deserializes environment variables into a struct.

cargo add envy

Then use it like this:

use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Config {
    environment: String,
    app_user: String,
    port: u16,
}

fn main() {
    dotenvy::dotenv().ok(); // Load from .env

    let config: Config = envy::from_env().expect("Failed to load config");

    println!("{:?}", config.app_user);
    println!("{:?}", config.environment);
    println!("{:?}", config.port);
}
Enter fullscreen mode Exit fullscreen mode

⚠️Warning!!
Before using envy, make sure your environment variable names match your struct fields.

Note & Summary

  • dotenvy loads environment variables from a .env file into your Rust app.
  • envy maps those environment values to a Rust struct.
  • serde handles the deserialization and type conversion for your struct fields.

This combination keeps your configuration clean and type-safe, which is especially useful as the number of environment variables grows.

You can find the full example and source code in this GitHub repository: code

Top comments (0)