██▓ █ ██ ██ ▄█▀ ███▄ ▄███▓ ▓█████▄ ▓█████ ██▒ █▓ ▓██▒ ██ ▓██▒ ██▄█▒ ▓██▒▀█▀ ██▒ ▒██▀ ██▌▓█ ▀▓██░ █▒ ▒██░ ▓██ ▒██░▓███▄░ ▓██ ▓██░ ░██ █▌▒███ ▓██ █▒░ ▒██░ ▓▓█ ░██░▓██ █▄ ▒██ ▒██ ░▓█▄ ▌▒▓█ ▄ ▒██ █░░ ░██████▒▒▒█████▓ ▒██▒ █▄▒██▒ ░██▒ ██▓ ░▒████▓ ░▒████▒ ▒▀█░ ░ ▒░▓ ░░▒▓▒ ▒ ▒ ▒ ▒▒ ▓▒░ ▒░ ░ ░ ▒▓▒ ▒▒▓ ▒ ░░ ▒░ ░ ░ ▐░ ░ ░ ▒ ░░░▒░ ░ ░ ░ ░▒ ▒░░ ░ ░ ░▒ ░ ▒ ▒ ░ ░ ░ ░ ░░ ░ ░ ░░░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
Clap is perfect for configuring a web service
2025-06-15
While building a Rust web service, I wanted to better way to configure it on both production and my local development environment. In the development environment, I just want reasonable default values with the ability to override them with command-line arguments if I need to. But on production, configuration should propagate itself through environment variables.
The problem was that I could not find a crate that would do this both effectively and in an ergonomic way. Just as I was about to begin writing my own, I realised that the clap crate lets you add an env flag to each derive attribute. So Clap isn't just for parsing command-line arguments; by deriving Parser on my config struct and setting env and default_value attributes, I got a clean and predictable precedence: command-line > env var > default.
Here's a simple example:
use clap::Parser;
#[derive(Parser)]
#[command(name = &"My HTTP Server")]
#[command(version, about, long_about = None)]
struct Config {
/// Designates HOST IP address for the API
#[arg(long, env, default_value = &"127.0.0.1")]
pub api_ip: std::net::IpAddr,
/// Designates HOST port for the API.
#[arg(long, env, default_value_t = 58868, value_parser = clap::value_parser!(u16).range(1..))]
pub api_port: u16,
/// List whitelisted IP addresses
///
/// Some API endpoints require elevated privileges, and should only come from certain IP
/// addresses. The format should be a comma-separated string of valid IP addresses, for example;
/// 127.0.0.1,0.0.0.0
#[arg(long, env, num_args = 0.., value_delimiter = ',', default_value = &"127.0.0.1")]
pub api_ip_whitelist: Vec<std::net::IpAddr>,
}
Then I could just call parse
on the struct.
let config = Config::parse();
Easy. This gave me the flexibility to define values on production with environment variables (like in a container), override them with CLI args, and fall back to sensible defaults for development, all with minimal code.