Rust Application Configuration: config, envy, and dotenvy Libraries
When developing applications in Rust, configuration management is one of the key tasks. The flexibility, security, and ease of deployment depend on the right approach to loading settings. The Rust ecosystem has several mature solutions that allow reading configuration from files, environment variables, and other sources. In this article, we will look at three popular tools: config, envy, and dotenvy.
1. The config Library — A Universal Configuration Manager
config is a powerful library for loading hierarchical configurations. It supports multiple formats (JSON, TOML, YAML, HJSON, INI), as well as environment variables and remote sources. The main idea is to combine all sources into a single settings tree.
Installation
[dependencies]config = "0.14"serde = { version = "1.0", features = ["derive"] }Usage Example
use config::{Config, File, Environment};use serde::Deserialize;
#[derive(Debug, Deserialize)]struct AppConfig { server: ServerConfig, database: DatabaseConfig,}
#[derive(Debug, Deserialize)]struct ServerConfig { host: String, port: u16,}
#[derive(Debug, Deserialize)]struct DatabaseConfig { url: String, pool_size: u32,}
fn load_config() -> Result<AppConfig, config::ConfigError> { let cfg = Config::builder() .add_source(File::with_name("config/default")) // Default settings .add_source(File::with_name("config/local").required(false)) // Local overrides .add_source(Environment::with_prefix("APP")) // Environment variables with APP_ prefix .build()?;
cfg.try_deserialize()}
fn main() { match load_config() { Ok(config) => println!("Loaded config: {:?}", config), Err(e) => eprintln!("Config error: {}", e), }}The library automatically merges sources: values from subsequent sources overwrite previous ones. This makes it easy to organize a hierarchy: default → environment → local override.
2. The envy Library — Configuration from Environment Variables
envy is a minimalist library that loads configuration exclusively from environment variables. It is ideal for 12-factor applications where settings should be separated from the code.
Installation
[dependencies]envy = "0.4"serde = { version = "1.0", features = ["derive"] }Usage Example
use serde::Deserialize;
#[derive(Debug, Deserialize)]struct AppConfig { #[serde(rename = "SERVER_HOST")] host: String, #[serde(rename = "SERVER_PORT")] port: u16, #[serde(rename = "DATABASE_URL")] db_url: String,}
fn load_config() -> Result<AppConfig, envy::Error> { envy::from_env::<AppConfig>()}
fn main() { match load_config() { Ok(config) => println!("Env config: {:?}", config), Err(e) => eprintln!("Envy error: {}", e), }}To use it, simply set the environment variables:
export SERVER_HOST=127.0.0.1export SERVER_PORT=8080export DATABASE_URL=postgres://user:pass@localhost/dbenvy supports nested structures via separators (e.g., DATABASE__HOST), as well as optional fields with default values.
3. The dotenvy Library — Loading .env Files
dotenvy is a fork of the popular dotenv library, designed for loading environment variables from .env files. It is especially useful during development when you need to quickly set local settings.
Installation
[dependencies]dotenvy = "0.15"<