一个功能强大、类型安全的 Rust 配置管理系统,提供灵活的配置管理,支持多种数据类型、变量替换、多值属性,以及可插拔的配置来源(config source)(文件、环境变量与组合源)。
English | 简体中文
特性
- ✅ 纯泛型 API - 使用
get<T>()、read(ConfigField<T>)和set<T>()泛型方法,支持完整的类型推断 - ✅ 丰富的数据类型 - 支持所有基本类型、时间类型、字符串、字节数组等
- ✅ 多值属性 - 每个配置项可以包含多个值,支持列表操作
- ✅ 变量替换 - 支持
${var_name}形式的配置变量替换;如需回退到进程环境变量,必须显式开启 - ✅ 类型安全 - 编译期类型检查,避免运行时类型错误
- ✅ 序列化支持 - 完整的 serde 支持,可序列化和反序列化
- ✅ 可扩展 - 基于 trait 的设计,易于支持自定义类型
- ✅ 配置来源(ConfigSource) - 提供
ConfigSourcetrait 与多种内置实现:TOML、YAML、Java 风格.properties、.env文件、进程环境变量(可选前缀与键名规范化),以及按顺序合并多个来源的CompositeConfigSource(后加载的来源覆盖同名键);内置来源按事务语义加载,会校验有歧义的规范化 key,并拒绝 TOML/YAML 单文档内展平后的重复 key - ✅ 只读访问(ConfigReader) -
ConfigReadertrait 提供无需修改配置的泛型读取;Config与ConfigPrefixView均实现该 trait,并包含字符串辅助方法、多 key 读取和字段声明读取 - ✅ 可配置解析 -
ConfigReadOptions可在全局或单个字段上控制字符串 trim、空白值处理、布尔字面量和标量字符串拆分列表 - ✅ 前缀视图(ConfigPrefixView) -
Config::prefix_view返回绑定逻辑键前缀的ConfigPrefixView(相对键解析为前缀.键);可通过ConfigPrefixView::prefix_view嵌套子前缀 - ✅ 高效核心表示 - 核心值使用枚举表示,并通过 staged source loading 控制合并成本;可插拔 source 在需要动态组合时仍可使用 trait object
安装
在您的 Cargo.toml 中添加:
[dependencies]
qubit-config = "0.13"
默认 features 会保留完整 API:
qubit-config = { version = "0.13", default-features = true }
如果只需要轻量能力,可以关闭默认 features,再按需启用具体 source:
qubit-config = { version = "0.13", default-features = false, features = ["source-toml"] }
可用 feature flags:
| Feature | 启用内容 |
|---|---|
rich-types | 对 chrono、url、num-bigint、bigdecimal 类型的直接 FromConfig 支持 |
source-toml | TomlConfigSource 与 Config::from_toml_file |
source-yaml | YamlConfigSource 与 Config::from_yaml_file |
source-env-file | EnvFileConfigSource 与 Config::from_env_file |
快速开始
use qubit_config::Config;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut config = Config::new();
// 设置配置值
config.set("port", 8080)?;
config.set("host", "localhost")?;
config.set("debug", true)?;
// 读取配置值(类型推断)
let port: i32 = config.get("port")?;
let host: String = config.get("host")?;
let debug: bool = config.get("debug")?;
// 使用 turbofish 语法
let port = config.get::<i32>("port")?;
// 使用默认值
let timeout: u64 = config.get_or("timeout", 30)?;
println!("服务器运行在 {}:{}", host, port);
Ok(())
}
核心概念
Config(配置管理器)
Config 结构体是中心配置管理器,存储和管理所有配置属性。
let mut config = Config::new();
config.set("database.host", "localhost")?;
config.set("database.port", 5432)?;
Property(配置属性)
每个配置项由一个 Property 表示,包含:
- 名称(键)
- 多值容器
- 可选描述
- final 标志(设置后不可变)
MultiValues(多值容器)
一个类型安全的容器,可以保存相同数据类型的多个值。
ConfigReader(只读接口)
ConfigReader 是配置的只读抽象。仅需读取配置时,函数或类型可以接受 &impl ConfigReader(或泛型 R: ConfigReader),而不必暴露完整的 &Config;同一套 API 可用于完整 Config 和 ConfigPrefixView。ConfigReader 包含泛型类型读取方法,因此不是 object-safe,不能用作 dyn ConfigReader。
主要读取 API 如下:
| API | 行为 |
|---|---|
get<T>(name) | 通过 FromConfig 读取必填值。 |
get_optional<T>(name) | key 缺失或为空时返回 Ok(None)。 |
get_or<T>(name, default) | 仅在 key 缺失或为空时使用默认值。 |
get_any<T>(&[names]) | 按顺序读取第一个存在且非空的 key。 |
get_optional_any<T>(&[names]) | 多 key 可选读取。 |
get_any_or<T>(&[names], default) | 多 key 默认值读取。 |
get_any_or_with<T>(&[names], default, options) | 使用显式读取选项的多 key 默认值读取。 |
get_string、get_string_any、get_string_any_or | 带变量替换的字符串读取。 |
read(ConfigField<T>) | 通过字段声明读取,支持 name、alias、default 和字段级解析选项。 |
get_strict / get_list_strict | 精确存储类型读取,不做跨类型转换。 |
默认值不会隐藏错误配置。如果 key 存在,但值解析、类型转换或变量替换失败,会直接返回错误,不会回退到默认值,也不会继续尝试后面的 alias。
use qubit_config::{Config, ConfigError};
let mut config = Config::new();
config.set("worker.threads", "abc")?;
let missing = config.get_or("missing.threads", 4u16)?;
assert_eq!(missing, 4);
let invalid = config.get_or("worker.threads", 4u16);
assert!(matches!(invalid, Err(ConfigError::ConversionError { .. })));
get_or、get_any_or、get_any_or_with 等带 default value 的读取接口现在支持更方便的默认值传法。标量默认值仍直接使用目标类型;字符串默认值可以直接传 &str;字符串列表默认值可以使用数组、切片或借用的 Vec<String>。单 key 和多 key 参数也支持直接传数组、切片、vector 和借用的 vector。
let host = config.get_or::<String>("server.host", "localhost")?;
let paths = config.get_or::<Vec<String>>("server.paths", ["bin", "lib"])?;
let paths = config.get_any_or::<Vec<String>>(
["server.paths", "SERVER_PATHS"],
["cache", "tmp"],
)?;
ConfigPrefixView(前缀视图)
ConfigPrefixView 表示对 Config 的零拷贝借用,并带有一个逻辑键前缀。通过 Config::prefix_view 创建;传入的键名会在该前缀下解析。例如前缀 db、键 host 对应存储键 db.host。使用 ConfigPrefixView::prefix_view 可得到嵌套前缀视图。
use qubit_config::{Config, ConfigReader};
let mut config = Config::new();
config.set("db.host", "localhost")?;
config.set("db.port", 5432i32)?;
let db = config.prefix_view("db");
let host: String = db.get_string("host")?;
let port: i32 = db.get("port")?;
ConfigReadOptions(读取解析选项)
ConfigReadOptions 控制配置值如何被解析。它可以设置在 Config 全局上,也可以附加到单个 ConfigField<T> 上。
| 选项组 | 控制内容 |
|---|---|
StringReadOptions | 字符串 trim,以及空白字符串的处理方式:保留、当作缺失、或拒绝。 |
BooleanReadOptions | 可接受的布尔字面量和大小写敏感性。 |
CollectionReadOptions | 是否把标量字符串拆成列表、分隔符、元素 trim,以及空元素策略。 |
| 环境变量替换 | 未解析的 ${...} 占位符是否可以回退读取进程环境变量。默认关闭。 |
ConfigReadOptions::env_friendly() 适合环境变量风格配置:会 trim 字符串,把空白标量字符串当作缺失,布尔值接受 true/false、1/0、yes/no、on/off,并在读取 Vec<T> 时按逗号拆分标量字符串、跳过空元素。
${...} 替换回退读取进程环境变量默认关闭,ConfigReadOptions::env_friendly() 也不会自动开启,以避免进程环境值带来意外注入。仅在可信配置链路中用 with_env_variable_substitution_enabled(true) 显式开启。
use qubit_config::{Config, options::ConfigReadOptions};
let mut config = Config::new().with_read_options(ConfigReadOptions::env_friendly());
config.set("HTTP_ENABLED", "yes")?;
config.set("HTTP_PORTS", "8080, 8081,,8082")?;
let enabled: bool = config.get("HTTP_ENABLED")?;
let ports: Vec<u16> = config.get("HTTP_PORTS")?;
assert!(enabled);
assert_eq!(ports, vec![8080, 8081, 8082]);
也可以用 builder 风格方法构造更严格或更贴合业务的解析选项:
use qubit_config::{
Config,
options::{
BooleanReadOptions, CollectionReadOptions, ConfigReadOptions, EmptyItemPolicy,
},
};
let options = ConfigReadOptions::default()
.with_boolean_options(
BooleanReadOptions::strict()
.with_true_literal("enabled")
.with_false_literal("disabled"),
)
.with_collection_options(
CollectionReadOptions::default()
.with_split_scalar_strings(true)
.with_delimiters([',', ';'])
.with_trim_items(true)
.with_empty_item_policy(EmptyItemPolicy::Reject),
);
let mut config = Config::new().with_read_options(options);
config.set("feature", "enabled")?;
config.set("ports", "8080; 8081")?;
let feature: bool = config.get("feature")?;
let ports: Vec<u16> = config.get("ports")?;
ConfigField(字段声明读取)
当一个逻辑配置项有别名、默认值或字段级解析规则时,使用 ConfigField<T>。这样迁移 key、旧 key 和环境变量风格 key 都可以留在配置声明里,而不是散落到业务代码中。
use qubit_config::{Config, field::ConfigField, options::ConfigReadOptions};
let mut config = Config::new();
config.set("MIME_DETECTOR_ENABLE_PRECISE_DETECTION", "yes")?;
let enabled = config.read(
ConfigField::<bool>::builder()
.name("mime.enable_precise_detection")
.alias("MIME_DETECTOR_ENABLE_PRECISE_DETECTION")
.alias("ANOTHER_MIME_DETECTOR_ENABLE_PRECISE_DETECTION_PROPERTY")
.default(false)
.read_options(ConfigReadOptions::env_friendly())
.build(),
)?;
assert!(enabled);
builder 会强制主 key 明确出现:只有调用 name(...) 后,才可以调用 build() 生成 ConfigField<T>。
多 Key 读取
当完整的 ConfigField<T> 显得过重时,可以使用 get_any、get_optional_any、get_any_or 和 get_any_or_with 做轻量 alias 读取。
use qubit_config::{Config, options::ConfigReadOptions};
let mut config = Config::new().with_read_options(ConfigReadOptions::env_friendly());
config.set("SERVICE_URL", "http://localhost:8080")?;
config.set("SERVER_TIMEOUT", "30")?;
let url = config.get_string_any(["service.url", "SERVICE_URL"])?;
let timeout = config.get_any_or(["server.timeout", "SERVER_TIMEOUT"], 10u64)?;
let optional_port = config.get_optional_any::<u16>(["server.port", "SERVER_PORT"])?;
let retries = config.get_any_or_with(
["server.retries", "SERVER_RETRIES"],
3u8,
&ConfigReadOptions::env_friendly(),
)?;
assert_eq!(url, "http://localhost:8080");
assert_eq!(timeout, 30);
assert_eq!(optional_port, None);
assert_eq!(retries, 3);
多 key 读取会按顺序扫描 key。缺失和空值会被跳过;第一个存在且非空的值会被解析。如果这个值无效,会直接返回错误,不会继续尝试后面的 key。
配置来源(Configuration sources)
ConfigSource 的实现负责把外部设置写入 Config。可调用 merge_from_source,或在持有 &mut Config 时对具体来源调用 load。如果不需要在加载前定制目标 Config,可以直接使用 Config::from_toml_file、Config::from_yaml_file、Config::from_properties_file、Config::from_env_file、Config::from_env 或 Config::from_env_prefix 等便捷构造方法。文件 source 的便捷构造方法需要启用对应的 source-* feature。
内置来源和 Config::merge_from_source 都按事务语义加载:如果解析或合并失败,目标 Config 会保留加载前的状态。
会规范化 key 的环境变量 source 会拒绝空 key 或畸形点号路径,例如 APP_、APP__DB、APP_DB__HOST。TOML 和 YAML source 也会拒绝单个文档内展平后的重复 key,例如字面量 "server.port" 与嵌套的 server.port 发生冲突。
| 类型 | 作用 |
|---|---|
TomlConfigSource | 读取 TOML 文件;嵌套表展平为点号分隔键 |
YamlConfigSource | 读取 YAML 文件;嵌套映射同样展平 |
PropertiesConfigSource | Java .properties 文件 |
EnvFileConfigSource | .env 风格文件 |
EnvConfigSource | 进程环境变量;可选前缀过滤与键名规范化(例如 APP_SERVER_HOST → server.host) |
CompositeConfigSource | 按顺序组合多个来源;后出现者覆盖同名键(并受 Property 的 final 语义约束) |
use qubit_config::{Config, source::{
CompositeConfigSource, ConfigSource, EnvConfigSource, TomlConfigSource,
}};
let mut config = Config::new();
let mut composite = CompositeConfigSource::new();
composite
.add(TomlConfigSource::from_file("config.toml"))
.add(EnvConfigSource::with_prefix("APP_"));
config.merge_from_source(&composite)?;
use qubit_config::Config;
let config = Config::from_toml_file("config.toml")?;
let env_config = Config::from_env_prefix("APP_")?;
使用示例
基本配置
use qubit_config::Config;
let mut config = Config::new();
// 设置各种类型
config.set("port", 8080)?;
config.set("host", "localhost")?;
config.set("debug", true)?;
config.set("timeout", 30.5)?;
config.set("is_use_prefix", "0")?;
// 使用类型推断和转换语义获取值
let port: i32 = config.get("port")?;
let host: String = config.get("host")?;
let debug: bool = config.get("debug")?;
let is_use_prefix: bool = config.get("is_use_prefix")?;
// 需要精确存储类型时仍可使用 strict 读取
assert!(config.get_strict::<bool>("is_use_prefix").is_err());
多值配置
// 设置多个值
config.set("ports", vec![8080, 8081, 8082])?;
// 获取所有值
let ports: Vec<i32> = config.get_list("ports")?;
// 逐个添加值
config.set("server", "server1")?;
config.add("server", "server2")?;
config.add("server", "server3")?;
let servers: Vec<String> = config.get_list("server")?;
变量替换
config.set("host", "localhost")?;
config.set("port", "8080")?;
config.set("url", "http://${host}:${port}/api")?;
// 变量会自动替换
let url = config.get_string("url")?;
// 结果: "http://localhost:8080/api"
// 回退读取环境变量需要显式开启,因为进程环境值在某些部署中可能被外部影响。
config.set_read_options(
qubit_config::options::ConfigReadOptions::default()
.with_env_variable_substitution_enabled(true),
);
std::env::set_var("APP_ENV", "production");
config.set("env", "${APP_ENV}")?;
let env = config.get_string("env")?;
// 结果: "production"
结构化配置
deserialize() 使用与泛型 get 读取一致的读取选项。例如,ConfigReadOptions::env_friendly() 可在反序列化 serde 结构时解析数字字符串、布尔别名、逗号分隔的标量字符串列表,并把空白字符串按缺失值处理。
当 prefix 非空时,deserialize(prefix) 使用严格的根选择语义:如果存在精确的 prefix 属性,就把该属性作为反序列化根值;否则用 prefix. 子键组成根对象。同时定义 prefix 和 prefix. 会返回 key conflict。带点号的键也必须能组成无歧义对象树,例如同一反序列化对象中不能同时存在 a 和 a.b。
use qubit_config::{Config, options::ConfigReadOptions};
#[derive(Debug)]
struct DatabaseConfig {
host: String,
port: i32,
username: String,
password: String,
}
let mut config = Config::new();
config.set("db.host", "localhost")?;
config.set("db.port", 5432)?;
config.set("db.username", "admin")?;
config.set("db.password", "secret")?;
let db_config = DatabaseConfig {
host: config.get("db.host")?,
port: config.get("db.port")?,
username: config.get("db.username")?,
password: config.get("db.password")?,
};
可配置对象
use qubit_config::{Configurable, Configured};
// 使用 Configured 基类
let mut configured = Configured::new();
configured.config_mut().set("port", 3000)?;
configured.update_config(|config| {
config.set("host", "localhost")?;
config.set("workers", 4)?;
Ok(())
})?;
// 自定义可配置对象
struct Application {
configured: Configured,
}
impl Application {
fn new() -> Self {
Self {
configured: Configured::new(),
}
}
fn config(&self) -> &Config {
self.configured.config()
}
fn config_mut(&mut self) -> &mut Config {
self.configured.config_mut()
}
}
let mut app = Application::new();
app.config_mut().set("port", 3000)?;
config_mut() 提供直接可变访问,不会自动触发 on_config_changed()。如果希望一组修改成功后只触发一次回调,请使用 update_config()。
支持的数据类型
| Rust 类型 | 说明 | 示例 |
|---|---|---|
bool | 布尔值;字符串读取默认接受 true / false 和 1 / 0;ConfigReadOptions::env_friendly() 还接受 yes / no 和 on / off | true, false, "0", "yes" |
char | 字符 | 'a', '中' |
i8, i16, i32, i64, i128 | 有符号整数 | 42, -100 |
u8, u16, u32, u64, u128 | 无符号整数 | 255, 1000 |
f32, f64 | 浮点数 | 3.14, 2.718 |
String | 字符串 | "hello", "世界" |
Vec<T> | 列表值;配合集合读取选项时,可把标量字符串拆成列表元素 | [1, 2, 3], "a,b,c" |
chrono::NaiveDate | 日期 | 2025-01-01 |
chrono::NaiveTime | 时间 | 12:30:45 |
chrono::NaiveDateTime | 日期时间 | 2025-01-01 12:30:45 |
chrono::DateTime<Utc> | 带时区的日期时间 | 2025-01-01T12:30:45Z |
扩展自定义类型
要支持业务特定的配置读取,为目标类型实现 FromConfig。实现中可以复用内置的 FromConfig 解析,再叠加业务校验;调用方仍然使用 config.get::<T>()、config.get_or::<T>() 或 config.read(ConfigField::<T>),不需要在每个调用点手写 parse 代码。
use qubit_config::{Config, ConfigError, ConfigResult, Property};
use qubit_config::from::{ConfigParseContext, FromConfig};
#[derive(Debug, Clone, PartialEq)]
struct Port(u16);
impl Port {
fn new(value: u16) -> Result<Self, String> {
if value < 1024 {
Err("端口号必须 >= 1024".to_string())
} else {
Ok(Port(value))
}
}
fn value(&self) -> u16 {
self.0
}
}
impl FromConfig for Port {
fn from_config(property: &Property, ctx: &ConfigParseContext<'_>) -> ConfigResult<Self> {
let value = u16::from_config(property, ctx)?;
Port::new(value).map_err(|message| ConfigError::ConversionError {
key: ctx.key().to_string(),
message,
})
}
}
let mut config = Config::new();
config.set("port", "8080")?;
let port: Port = config.get("port")?;
let fallback = config.get_or("fallback_port", Port::new(8080).unwrap())?;
只有当你还需要直接存储自定义类型,或需要通过 get_strict / get_list_strict 做精确存储类型读取时,才需要实现更底层的 qubit_value trait。
API 设计哲学
为什么选择纯泛型 API?
我们采用纯泛型方案(如 get<T>()、set<T>()、get_or<T>()、read(ConfigField<T>)),而不是为每个类型提供专门的方法(如 get_i32()、get_bool() 等),原因如下:
- 通用性强 - 泛型方法可以处理任何实现了相应 trait 的类型,包括自定义类型
- 代码简洁 - 避免大量重复的类型特定方法
- 易于维护 - 添加新类型只需实现 trait,无需修改 Config 结构体
- 符合 Rust 惯用法 - 充分利用 Rust 的类型系统和类型推断
类型推断的三种方式
// 1. 变量类型标注(推荐,最清晰)
let port: i32 = config.get("port")?;
// 2. Turbofish 语法(需要时使用)
let port = config.get::<i32>("port")?;
// 3. 从上下文推断(最简洁)
struct Server {
port: i32,
}
let server = Server {
port: config.get("port")?, // 从字段类型推断
};
错误处理
配置系统使用 ConfigResult<T> 类型进行错误处理:
pub enum ConfigError {
PropertyNotFound(String), // 配置项不存在
PropertyHasNoValue(String), // 配置项没有值
TypeMismatch { key: String, expected: DataType, actual: DataType }, // 类型不匹配
ConversionError { key: String, message: String }, // 类型转换失败
IndexOutOfBounds { index: usize, len: usize }, // 索引越界
SubstitutionError(String), // 变量替换失败
SubstitutionDepthExceeded(usize), // 变量替换深度超限
SubstitutionCycle { chain: Vec<String> }, // 检测到变量替换环
MergeError(String), // 配置合并失败
PropertyIsFinal(String), // 配置项是最终的,不能被覆盖
KeyConflict { path: String, existing: String, incoming: String }, // key 结构有歧义
IoError(std::io::Error), // IO 错误
ParseError(String), // 解析错误
DeserializeError { path: String, message: String, source: Option<Box<ConfigError>> },
Other(String), // 其他错误
}
性能考虑
- 枚举值存储 - 核心配置值使用枚举表示,便于稳定地存储和转换
- 变量替换优化 - 使用
OnceLock缓存正则表达式,避免重复编译 - 高效存储 - 配置项使用
HashMap存储,查找时间复杂度 O(1) - 分阶段 source 加载 - 内置 source 在 composite 和 merge 路径中直接写入已 staged 的
Config,保留事务语义,同时避免重复整份配置克隆
测试
运行测试套件:
cargo test
运行代码覆盖率测试:
./coverage.sh
文档
详细的 API 文档请访问 docs.rs/qubit-config。
内部设计文档请参阅 src/README.md。
依赖项
qubit-datatype- 核心工具和数据类型定义qubit-value- 值处理框架serde- 序列化框架regex- 正则表达式支持chrono、url、num-bigint、bigdecimal- 默认rich-typesfeature 下的直接富类型解析支持toml-source-tomlfeature 下的 TOML 解析serde_norway-source-yamlfeature 下的 YAML 解析dotenvy-source-env-filefeature 下的.env文件解析
发展路线图
- [ ] 更多配置格式加载器(如 JSON、XML)
- [ ] 除有序
CompositeConfigSource外的高级合并 / 覆盖策略 - [ ] 支持配置监听和热重载
- [ ] 支持配置验证框架
- [ ] 支持配置加密
- [ ] 提供线程安全的包装类型
SyncConfig
贡献
欢迎贡献!请随时提交 Pull Request。
许可证
Copyright (c) 2025 - 2026. Haixing Hu, Qubit Co. Ltd. All rights reserved.
根据 Apache 许可证 2.0 版("许可证")授权; 除非遵守许可证,否则您不得使用此文件。 您可以在以下位置获取许可证副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律要求或书面同意,否则根据许可证分发的软件 按"原样"分发,不附带任何明示或暗示的担保或条件。 有关许可证下的特定语言管理权限和限制,请参阅许可证。
完整的许可证文本请参阅 LICENSE。
作者
胡海星 - Qubit Co. Ltd.
---
有关 Qubit Rust 库的更多信息,请访问我们的 GitHub 组织。