The config crate provides a unified interface to load settings from multiple sources (files, environment variables, command-line arguments) with automatic type conversion and merging. You define a Rust struct annotated with #[derive(Deserialize)], create a Config builder, add your sources in order of precedence, and then call try_deserialize to populate your struct.
Here is a practical example using a YAML file and environment variables, where environment variables override file values:
use config::{Config, ConfigError, File};
use serde::Deserialize;
use std::error::Error;
#[derive(Debug, Deserialize)]
struct Settings {
app_name: String,
port: u16,
debug: bool,
}
fn main() -> Result<(), Box<dyn Error>> {
// 1. Create a builder and add sources in order of precedence (lowest to highest)
let settings = Config::builder()
// Load defaults (optional)
.set_default("port", 8080)?
// Load from a YAML file
.add_source(File::with_name("config").required(false))
// Load from environment variables (prefix "MY_APP_" maps to struct fields)
.add_source(config::Environment::with_prefix("MY_APP").separator("__"))
.build()?;
// 2. Deserialize the merged configuration into your struct
let settings: Settings = settings.try_deserialize()?;
println!("App: {}, Port: {}, Debug: {}", settings.app_name, settings.port, settings.debug);
Ok(())
}
In this setup, config.yaml might contain app_name: "MyService" and port: 3000. If you run the binary with MY_APP__PORT=9000, the final port value will be 9000 because the environment source was added last and overrides the file. Note the double underscore separator in the environment variable name (MY_APP__PORT), which maps to the port field in the struct.
For more complex scenarios, you can add command-line arguments or JSON sources. The crate handles merging automatically, so you don't need to manually parse each format. If a required field is missing from all sources, try_deserialize will return an error, allowing you to fail fast during startup.
Common pitfalls include forgetting to add the serde dependency (required for Deserialize) and mismatching the environment variable prefix or separator. Always check the ConfigError type to understand why deserialization failed, as it often points to specific missing keys or type mismatches.