Конфигурация приложений Rust: библиотеки config и dotenv
Любое современное приложение требует гибкой системы конфигурации: настройки базы данных, API-ключи, порты сервера, уровни логирования и другие параметры. В экосистеме Rust для этого есть мощные инструменты — крейты config и dotenv. Они позволяют объединять настройки из разных источников (файлы, переменные окружения, аргументы командной строки) в единую типобезопасную структуру.
Зачем нужна отдельная библиотека?
Rust — язык с сильной статической типизацией. Простое чтение строк из std::env::var() приводит к множеству ручных проверок и преобразований. Библиотека config автоматизирует этот процесс:
- Поддержка форматов: YAML, JSON, TOML, HJSON, INI, RON
- Иерархическая структура настроек с возможностью переопределения
- Автоматическая десериализация в пользовательские структуры через
serde - Объединение нескольких источников с приоритетами (файл < env < CLI)
Установка
Добавьте в Cargo.toml:
[dependencies]
config = "0.14"
serde = { version = "1.0", features = ["derive"] }
dotenv = "0.15"
Базовый пример: чтение из файла и env
Создадим структуру для конфигурации приложения:
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> {
// Загружаем конфигурацию
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(())
}
Файл config/default.toml:
[server]
host = "127.0.0.1"
port = 8080
workers = 4
[database]
url = "postgres://localhost/mydb"
pool_size = 10
Переменные окружения с префиксом APP_ переопределяют значения из файла. Например, APP_DATABASE__URL изменит URL базы данных.
Работа с dotenv
Для удобства разработки часто используют файл .env. Крейт dotenv загружает его в переменные окружения перед инициализацией config:
use dotenv::dotenv;
fn main() {
dotenv().ok(); // загружаем .env
// далее инициализация config как в примере выше
}
Содержимое .env:
APP_SERVER__PORT=3000
APP_DATABASE__POOL_SIZE=20
Продвинутый сценарий: несколько источников
Реальные проекты требуют комбинирования настроек из разных мест. config позволяет задать цепочку источников с приоритетом:
let config = Config::builder()
// Базовые настройки (низкий приоритет)
.add_source(config::File::with_name("config/default"))
// Переопределение для конкретного окружения
.add_source(config::File::with_name(&format!("config/{}", env::var("APP_ENV").unwrap_or("development"))))
// Переменные окружения (средний приоритет)
.add_source(config::Environment::with_prefix("APP"))
// Аргументы командной строки (высокий приоритет)
.add_source(config::File::from(std::env::args().nth(1).unwrap_or_default()))
.build()?;
Best Practices
- Используйте префиксы для переменных окружения, чтобы избежать конфликтов с системными переменными
- Разделяйте настройки по окружениям:
config/development.toml,config/production.toml - Не храните секреты в репозитории — используйте
.env(добавьте его в.gitignore) или внешние сервисы (Vault, AWS Secrets Manager) - Валидируйте кон