Rust Application Configuration: config and dotenv Libraries

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Rust Application Configuration: config and dotenv Libraries



Any modern application requires a flexible configuration system: database settings, API keys, server ports, logging levels, and other parameters. In the Rust ecosystem, there are powerful tools for this — the config and dotenv crates. They allow you to combine settings from different sources (files, environment variables, command-line arguments) into a single type-safe structure.



Why a separate library?

Rust is a language with strong static typing. Simply reading strings from std::env::var() leads to numerous manual checks and conversions. The config library automates this process:

  • Format support: YAML, JSON, TOML, HJSON, INI, RON
  • Hierarchical configuration structure with override capability
  • Automatic deserialization into custom structures via serde
  • Merging multiple sources with priorities (file < env < CLI)


Installation

Add to Cargo.toml:

[dependencies]config = "0.14"serde = { version = "1.0", features = ["derive"] }dotenv = "0.15"


Basic example: reading from file and env

Let's create a structure for the application configuration:

use serde::Deserialize;use config::Config;

#[derive(Debug, Deserialize)]struct AppConfig { server: ServerConfig, database: DatabaseConfig,}

#[derive(Debug, Deserialize)]struct ServerConfig { host: String, port: u16, workers: u8,}

#[derive(Debug, Deserialize)]struct DatabaseConfig { url: String, pool_size: u32,}

fn main() -> Result<(), config::ConfigError> { // Load configuration let config = Config::builder() .add_source(config::File::with_name("config/default")) .add_source(config::Environment::with_prefix("APP")) .build()?;

let app_config: AppConfig = config.try_deserialize()?; println!("{:#?}", app_config); Ok(())}


File config/default.toml:

[server]host = "127.0.0.1"port = 8080workers = 4

[database]url = "postgres://localhost/mydb"pool_size = 10


Environment variables with the APP_ prefix override values from the file. For example, APP_DATABASE__URL will change the database URL.



Working with dotenv

For development convenience, a .env file is often used. The dotenv crate loads it into environment variables before initializing config:

use dotenv::dotenv;

fn main() { dotenv().ok(); // load .env // then initialize config as in the example above}


Contents of .env:

APP_SERVER__PORT=3000APP_DATABASE__POOL_SIZE=20


Advanced scenario: multiple sources

Real projects require combining settings from different places. config allows you to specify a chain of sources with priority:

let config = Config::builder()    // Base settings (low priority)    .add_source(config::File::with_name("config/default"))    // Override for a specific environment    .add_source(config::File::with_name(&format!("config/{}", env::var("APP_ENV").unwrap_or("development"))))    // Environment variables (medium priority)    .add_source(config::Environment::with_prefix("APP"))    // Command-line arguments (high priority)    .add_source(config::File::from(std::env::args().nth(1).unwrap_or_default()))    .build()?;


Best Practices

  • Use prefixes for environment variables to avoid conflicts with system variables

Recommendations