目录
Rust toml 库详细使用教程
toml 是 Rust 生态中常用的 TOML 编码、解码库,支持通过 Serde 在 TOML 文本与 Rust 数据结构之间转换。它的定位类似于 serde_json:负责把配置文本解析成 Rust 对象,也负责把 Rust 对象序列化成 TOML。当前文档对应 1.1.x 版本,并支持 TOML 1.1.0 规范。(Docs.rs)
最常见的使用流程是:
application.toml
↓ 读取文件
String
↓ toml::from_str()
Rust 配置结构体
反过来则是:
Rust 配置结构体
↓ toml::to_string_pretty()
String
↓ 写入文件
application.toml
一、创建项目并安装依赖
创建 Rust 2024 项目:
cargo new toml-demo cd toml-demo
添加依赖:
cargo add toml cargo add serde --features derive
对应的 Cargo.toml:
[package]
name = "toml-demo"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", features = ["derive"] }
toml = "1"
这里推荐写:
toml = "1"
而不是锁死某个补丁版本,例如 1.1.2。这样 Cargo 可以自动使用兼容的 1.x 版本。
toml 默认启用了 parse、display、serde 和 std 功能,普通配置文件读写不需要再额外指定 features。(Docs.rs)
二、先了解 TOML 基本语法
1. 基本数据类型
# 字符串 name = "IT工单系统" # 整数 port = 5800 # 浮点数 version_number = 1.5 # 布尔值 enabled = true # 数组 features = ["rbac", "ticket", "asset"] # 日期时间 start_time = 2026-07-19T20:30:00+08:00
TOML 支持字符串、整数、浮点数、布尔值、日期时间、数组、内联表和普通表,并且区分大小写。(toml.io)
对应的 Rust 类型通常为:
| TOML 类型 | Rust 类型 |
|---|---|
"hello" |
String |
8080 |
i64、u16、u32 等 |
3.14 |
f64 |
true |
bool |
[1, 2, 3] |
Vec<i32> |
[server] |
Rust 结构体 |
[[servers]] |
Vec<Server> |
| 日期时间 | toml::value::Datetime |
2. 表 Table
TOML 中的表类似于 JSON 对象或 Rust 结构体:
[server] host = "0.0.0.0" port = 5800 workers = 8
可以理解为:
{
"server": {
"host": "0.0.0.0",
"port": 5800,
"workers": 8
}
}
3. 嵌套表
[database] url = "mysql://root:123456@127.0.0.1:3306/workorder" [database.pool] min_connections = 5 max_connections = 20
对应的层级结构:
database
├── url
└── pool
├── min_connections
└── max_connections
4. 表数组
当配置中有多个服务器、上游服务或节点时,可以使用 [[名称]]:
[[upstreams]] name = "user-service" url = "http://127.0.0.1:8101" timeout_seconds = 5 [[upstreams]] name = "ticket-service" url = "http://127.0.0.1:8102" timeout_seconds = 10
它对应 Rust 中的:
Vec<UpstreamConfig>
每出现一次 [[upstreams]],就会向数组增加一个对象。(toml.io)
5. TOML 没有 null
下面这种写法是错误的:
redis_url = null
TOML 没有 JSON 那样的 null 类型。没有值时,一般直接不写该配置项,然后在 Rust 中使用:
redis_url: Option<String>
TOML 规范要求配置值必须是明确的数据类型,不能只写键而不提供值。(toml.io)
三、使用 toml::Table 动态读取配置
当配置结构不固定,或者只是临时读取几个字段时,可以使用:
toml::Table
toml::Table 本质上是:
Map<String, toml::Value>
而 toml::Value 可以表示字符串、整数、浮点数、布尔值、日期时间、数组和表。(Docs.rs)
示例
use std::error::Error;
use std::io;
use toml::{Table, Value};
fn main() -> Result<(), Box<dyn Error>> {
let content = r#"
title = "IT工单系统"
[server]
host = "127.0.0.1"
port = 5800
enabled = true
"#;
let table: Table = content.parse()?;
let title = table
.get("title")
.and_then(Value::as_str)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"缺少 title 配置",
)
})?;
let server = table
.get("server")
.and_then(Value::as_table)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"缺少 [server] 配置",
)
})?;
let host = server
.get("host")
.and_then(Value::as_str)
.unwrap_or("0.0.0.0");
let port = server
.get("port")
.and_then(Value::as_integer)
.unwrap_or(8080);
let enabled = server
.get("enabled")
.and_then(Value::as_bool)
.unwrap_or(false);
println!("系统名称:{title}");
println!("监听地址:{host}:{port}");
println!("是否启用:{enabled}");
Ok(())
}
输出:
系统名称:IT工单系统 监听地址:127.0.0.1:5800 是否启用:true
toml::Value 常用方法
value.as_str() value.as_integer() value.as_float() value.as_bool() value.as_datetime() value.as_array() value.as_table()
可变操作对应:
value.as_array_mut() value.as_table_mut()
所有这些方法返回 Option。类型不匹配时返回 None,而不是直接报错。(Docs.rs)
例如:
let port = server
.get("port")
.and_then(Value::as_integer);
假设配置写成:
port = "5800"
因为这里是字符串,不是整数,所以:
Value::as_integer()
会返回:
None
四、动态修改 TOML
use std::error::Error;
use std::io;
use toml::{Table, Value};
fn main() -> Result<(), Box<dyn Error>> {
let content = r#"
[server]
host = "127.0.0.1"
port = 5800
"#;
let mut table: Table = content.parse()?;
let server = table
.get_mut("server")
.and_then(Value::as_table_mut)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"缺少 [server] 配置",
)
})?;
server.insert(
"port".to_string(),
Value::Integer(8080),
);
server.insert(
"workers".to_string(),
Value::Integer(8),
);
let output = toml::to_string_pretty(&table)?;
println!("{output}");
Ok(())
}
输出类似:
[server] host = "127.0.0.1" port = 8080 workers = 8
五、推荐方式:反序列化为 Rust 结构体
在正式项目中,更推荐把 TOML 直接转换成强类型结构体。
优势是:
- 字段名称明确;
- 字段类型明确;
- 缺少必要字段时直接报错;
- 字段类型错误时直接报错;
- IDE 可以自动补全;
- 业务代码不需要到处调用
get()。
核心方法是:
toml::from_str::<目标类型>(&toml字符串)
目标类型必须实现 Serde 的 Deserialize trait。(Docs.rs)
示例一:简单结构体映射
application.toml
name = "IT工单系统" debug = true [server] host = "0.0.0.0" port = 5800
src/main.rs
use serde::Deserialize;
use std::error::Error;
use std::fs;
#[derive(Debug, Deserialize)]
struct AppConfig {
name: String,
debug: bool,
server: ServerConfig,
}
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
}
fn main() -> Result<(), Box<dyn Error>> {
let content = fs::read_to_string("application.toml")?;
let config: AppConfig = toml::from_str(&content)?;
println!("{config:#?}");
println!(
"{} 启动地址:http://{}:{}",
config.name,
config.server.host,
config.server.port
);
Ok(())
}
输出:
AppConfig {
name: "IT工单系统",
debug: true,
server: ServerConfig {
host: "0.0.0.0",
port: 5800,
},
}
IT工单系统启动地址:http://0.0.0.0:5800
这里的映射关系为:
name → AppConfig.name debug → AppConfig.debug [server] → AppConfig.server server.host → ServerConfig.host server.port → ServerConfig.port
六、完整的 Web 项目配置示例
下面结合 Rust 2024、Salvo、SeaORM、MySQL 和 Redis 的项目场景设计配置。
1. 配置文件
创建:
config/application.toml
内容:
environment = "development" [application] name = "workorder-api" version = "1.0.0" [server] bind-address = "0.0.0.0" port = 5800 workers = 8 request_timeout_seconds = 30 tls_enabled = false [database] url = "mysql://root:123456@127.0.0.1:3306/workorder" min_connections = 5 max_connections = 20 connect_timeout_seconds = 10 sql_logging = true [redis] url = "redis://127.0.0.1:6379" database = 0 pool_size = 10 [auth] access_token_expire_seconds = 7200 refresh_token_expire_seconds = 604800 [[upstreams]] name = "user-service" url = "http://127.0.0.1:8101" timeout_seconds = 5 [[upstreams]] name = "message-service" url = "http://127.0.0.1:8102" timeout_seconds = 10
2. 配置结构体
创建:
src/config.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AppConfig {
pub environment: Environment,
pub application: ApplicationConfig,
pub server: ServerConfig,
pub database: DatabaseConfig,
pub redis: Option<RedisConfig>,
pub auth: AuthConfig,
#[serde(default)]
pub upstreams: Vec<UpstreamConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Environment {
Development,
Test,
Production,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApplicationConfig {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ServerConfig {
#[serde(rename = "bind-address")]
pub bind_address: String,
pub port: u16,
#[serde(default = "default_workers")]
pub workers: usize,
#[serde(default = "default_request_timeout")]
pub request_timeout_seconds: u64,
#[serde(default)]
pub tls_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DatabaseConfig {
pub url: String,
#[serde(default = "default_min_connections")]
pub min_connections: u32,
#[serde(default = "default_max_connections")]
pub max_connections: u32,
#[serde(default = "default_connect_timeout")]
pub connect_timeout_seconds: u64,
#[serde(default)]
pub sql_logging: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RedisConfig {
pub url: String,
#[serde(default)]
pub database: i64,
#[serde(default = "default_redis_pool_size")]
pub pool_size: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuthConfig {
pub access_token_expire_seconds: u64,
pub refresh_token_expire_seconds: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpstreamConfig {
pub name: String,
pub url: String,
#[serde(default = "default_upstream_timeout")]
pub timeout_seconds: u64,
}
fn default_workers() -> usize {
4
}
fn default_request_timeout() -> u64 {
30
}
fn default_min_connections() -> u32 {
5
}
fn default_max_connections() -> u32 {
20
}
fn default_connect_timeout() -> u64 {
10
}
fn default_redis_pool_size() -> u32 {
10
}
fn default_upstream_timeout() -> u64 {
5
}
3. 加载配置
继续在 src/config.rs 中增加:
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub enum ConfigError {
Read {
path: PathBuf,
source: io::Error,
},
Parse {
path: PathBuf,
source: toml::de::Error,
},
}
impl fmt::Display for ConfigError {
fn fmt(
&self,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match self {
Self::Read { path, source } => {
write!(
formatter,
"读取配置文件失败:{},原因:{}",
path.display(),
source
)
}
Self::Parse { path, source } => {
write!(
formatter,
"解析配置文件失败:{},原因:{}",
path.display(),
source
)
}
}
}
}
impl Error for ConfigError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Read { source, .. } => Some(source),
Self::Parse { source, .. } => Some(source),
}
}
}
pub fn load_config(
path: impl AsRef<Path>,
) -> Result<AppConfig, ConfigError> {
let path = path.as_ref().to_path_buf();
let content = fs::read_to_string(&path).map_err(|source| {
ConfigError::Read {
path: path.clone(),
source,
}
})?;
toml::from_str(&content).map_err(|source| {
ConfigError::Parse {
path,
source,
}
})
}
这里把错误分成了两类:
ConfigError::Read
表示文件不存在、没有权限或读取失败。
ConfigError::Parse
表示文件能读取,但 TOML 格式错误,或者配置内容无法映射到 Rust 结构体。
4. 在 main.rs 中使用
mod config;
use config::load_config;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let config = load_config("config/application.toml")?;
println!("应用名称:{}", config.application.name);
println!(
"监听地址:{}:{}",
config.server.bind_address,
config.server.port
);
println!("运行环境:{:?}", config.environment);
println!("数据库地址:{}", config.database.url);
if let Some(redis) = &config.redis {
println!("Redis 地址:{}", redis.url);
}
for upstream in &config.upstreams {
println!(
"上游服务:{},地址:{}",
upstream.name,
upstream.url
);
}
Ok(())
}
七、常用 Serde 属性
toml 通过 Serde 进行结构体转换,因此 Serde 的字段属性非常重要。
1. #[serde(default)]
表示字段不存在时使用类型的默认值。
#[derive(Debug, Deserialize)]
struct ServerConfig {
#[serde(default)]
tls_enabled: bool,
}
配置文件没有:
tls_enabled = false
也不会报错,因为:
bool::default()
就是:
false
其他常见默认值:
String::default() → "" Vec::default() → [] bool::default() → false 整数默认值 → 0 Option::default() → None
2. 自定义默认值
fn default_port() -> u16 {
5800
}
#[derive(Debug, Deserialize)]
struct ServerConfig {
#[serde(default = "default_port")]
port: u16,
}
当 TOML 中没有 port 时:
config.port == 5800
3. Option<T>
可选配置使用 Option<T>:
#[derive(Debug, Deserialize)]
struct AppConfig {
redis: Option<RedisConfig>,
}
如果配置文件中没有:
[redis]
结果就是:
config.redis == None
如果存在 [redis],则是:
Some(RedisConfig { ... })
4. #[serde(rename = "...")]
Rust 字段名不能包含 -,但 TOML 键可以包含。
TOML:
bind-address = "0.0.0.0"
Rust:
#[derive(Debug, Deserialize)]
struct ServerConfig {
#[serde(rename = "bind-address")]
bind_address: String,
}
映射关系:
bind-address → bind_address
5. #[serde(rename_all = "lowercase")]
适合枚举。
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Environment {
Development,
Test,
Production,
}
TOML:
environment = "production"
解析结果:
Environment::Production
如果不加:
#[serde(rename_all = "lowercase")]
那么默认可能要求配置写成:
environment = "Production"
6. #[serde(deny_unknown_fields)]
用于禁止配置文件出现未知字段。
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ServerConfig {
host: String,
port: u16,
}
假设配置写错:
[server] host = "0.0.0.0" prot = 5800
这里把 port 错写成了 prot。
启用 deny_unknown_fields 后,会直接报错,而不是悄悄忽略。正式项目推荐在配置结构体上使用它,因为配置字段拼写错误通常应该阻止程序启动。TOML 反序列化错误类型能够表示未知字段、缺少字段、重复字段和错误类型等问题。(Docs.rs)
八、解析数组
TOML:
allowed_origins = [
"http://localhost:5173",
"https://admin.example.com",
]
Rust:
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct CorsConfig {
allowed_origins: Vec<String>,
}
读取:
for origin in &config.allowed_origins {
println!("{origin}");
}
混合类型数组
TOML 1.1 允许数组中出现不同类型:
values = [1, "hello", true]
规范允许这种混合数组。(toml.io)
但下面的 Rust 类型无法接收它:
values: Vec<String>
因为数组元素并不全是字符串。
动态配置可以使用:
values: Vec<toml::Value>
例如:
#[derive(Debug, Deserialize)]
struct Config {
values: Vec<toml::Value>,
}
处理:
for value in &config.values {
match value {
toml::Value::String(value) => {
println!("字符串:{value}");
}
toml::Value::Integer(value) => {
println!("整数:{value}");
}
toml::Value::Boolean(value) => {
println!("布尔值:{value}");
}
_ => {
println!("其他类型");
}
}
}
不过,业务配置最好保持数组类型一致。
九、使用 HashMap 读取动态表名
假设不同服务的名称是不固定的。
TOML
[services.user] base_url = "http://127.0.0.1:8101" timeout_seconds = 5 [services.ticket] base_url = "http://127.0.0.1:8102" timeout_seconds = 10 [services.message] base_url = "http://127.0.0.1:8103" timeout_seconds = 8
Rust
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
struct AppConfig {
services: HashMap<String, ServiceConfig>,
}
#[derive(Debug, Deserialize)]
struct ServiceConfig {
base_url: String,
timeout_seconds: u64,
}
使用:
for (name, service) in &config.services {
println!(
"服务名称:{},地址:{},超时:{} 秒",
name,
service.base_url,
service.timeout_seconds
);
}
访问指定服务:
if let Some(ticket_service) =
config.services.get("ticket")
{
println!(
"工单服务地址:{}",
ticket_service.base_url
);
}
需要注意,TOML 的表键是字符串,所以动态表通常应该映射为:
HashMap<String, ServiceConfig>
而不是:
HashMap<u64, ServiceConfig>
toml::Table::try_from() 在遇到非字符串 Map 键时可能转换失败。(Docs.rs)
十、把 Rust 结构体序列化成 TOML
需要为结构体派生:
Serialize
示例:
use serde::{Deserialize, Serialize};
use std::error::Error;
#[derive(Debug, Serialize, Deserialize)]
struct AppConfig {
name: String,
server: ServerConfig,
}
#[derive(Debug, Serialize, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
}
fn main() -> Result<(), Box<dyn Error>> {
let config = AppConfig {
name: "IT工单系统".to_string(),
server: ServerConfig {
host: "0.0.0.0".to_string(),
port: 5800,
},
};
let content = toml::to_string(&config)?;
println!("{content}");
Ok(())
}
to_string() 和 to_string_pretty()
普通序列化:
let content = toml::to_string(&config)?;
格式化序列化:
let content = toml::to_string_pretty(&config)?;
to_string_pretty() 会生成更适合人类阅读的 TOML 文本。(Docs.rs)
实际项目建议使用:
toml::to_string_pretty()
写入文件
use serde::Serialize;
use std::error::Error;
use std::fs;
#[derive(Debug, Serialize)]
struct ServerConfig {
host: String,
port: u16,
}
fn main() -> Result<(), Box<dyn Error>> {
let config = ServerConfig {
host: "0.0.0.0".to_string(),
port: 5800,
};
let content = toml::to_string_pretty(&config)?;
fs::write(
"generated.toml",
content,
)?;
println!("配置文件生成成功");
Ok(())
}
十一、修改已有配置后重新写入
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs;
#[derive(Debug, Serialize, Deserialize)]
struct AppConfig {
server: ServerConfig,
}
#[derive(Debug, Serialize, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
}
fn main() -> Result<(), Box<dyn Error>> {
let content =
fs::read_to_string("application.toml")?;
let mut config: AppConfig =
toml::from_str(&content)?;
config.server.port = 8080;
let new_content =
toml::to_string_pretty(&config)?;
fs::write(
"application.toml",
new_content,
)?;
Ok(())
}
流程为:
读取文件
↓
解析成结构体
↓
修改结构体
↓
序列化
↓
覆盖文件
十二、环境变量覆盖 TOML 配置
需要注意,toml 库不会自动处理这种占位符:
url = "${DATABASE_URL}"
它只会把 ${DATABASE_URL} 当成普通字符串。
可以先读取 TOML,再使用环境变量覆盖:
use std::env;
let mut config =
load_config("config/application.toml")?;
if let Ok(database_url) =
env::var("DATABASE_URL")
{
config.database.url = database_url;
}
if let Ok(redis_url) =
env::var("REDIS_URL")
{
if let Some(redis) = &mut config.redis {
redis.url = redis_url;
}
}
推荐的优先级是:
代码默认值
↓
application.toml
↓
环境变量
例如普通配置放在 TOML:
[database] min_connections = 5 max_connections = 20
敏感内容通过环境变量提供:
DATABASE_URL REDIS_URL JWT_SECRET
不要把生产数据库密码、JWT 密钥、云平台 Secret 等敏感数据提交到 Git 仓库中的 TOML 文件。
十三、TOML 日期时间
TOML 原生支持日期和时间。
配置文件
[release] released_at = 2026-07-19T20:30:00+08:00 maintenance_date = 2026-07-20
Rust
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Config {
release: ReleaseConfig,
}
#[derive(Debug, Deserialize)]
struct ReleaseConfig {
released_at: toml::value::Datetime,
maintenance_date: toml::value::Datetime,
}
使用:
println!(
"发布时间:{}",
config.release.released_at
);
toml::Value 中专门提供了 Datetime 类型,用于表示 TOML 的日期时间。(Docs.rs)
十四、解析错误处理
假设 TOML 写错:
[server] host = "0.0.0.0" port = "5800"
而 Rust 要求:
port: u16
解析代码:
match toml::from_str::<AppConfig>(&content) {
Ok(config) => {
println!("{config:#?}");
}
Err(error) => {
eprintln!("配置解析失败:{error}");
eprintln!("错误原因:{}", error.message());
if let Some(span) = error.span() {
eprintln!(
"错误字节范围:{}..{}",
span.start,
span.end
);
}
}
}
toml::de::Error 提供:
error.message() error.span()
其中 span() 返回错误在原始 TOML 文档中的字节范围;错误的 Display 输出通常还会包含行号、列号和具体解析上下文。(Docs.rs)
十五、常见错误
1. 必填字段缺失
Rust:
struct ServerConfig {
host: String,
port: u16,
}
TOML:
[server] host = "0.0.0.0"
因为缺少 port,解析失败。
解决方式之一是补配置:
port = 5800
也可以提供默认值:
#[serde(default = "default_port")] port: u16
或者改为可选:
port: Option<u16>
2. 类型不匹配
错误:
port = "5800"
正确:
port = 5800
因为:
"5800" → String 5800 → Integer
3. 布尔值大小写错误
错误:
enabled = True enabled = FALSE
正确:
enabled = true enabled = false
TOML 布尔值只能使用小写的 true 和 false。(toml.io)
4. 重复定义键
错误:
port = 5800 port = 8080
同一个键不能重复定义。(toml.io)
5. 重复定义表
错误:
[server] host = "0.0.0.0" [server] port = 5800
同一个普通表不能定义两次。应该合并为:
[server] host = "0.0.0.0" port = 5800
6. 配置文件路径错误
下面路径是相对于程序的“当前工作目录”,不一定是相对于 main.rs:
fs::read_to_string("config/application.toml")
执行:
cargo run
时,通常当前目录是项目根目录。
但部署为系统服务、Docker 容器或 Windows 服务后,当前目录可能发生变化。正式项目可以通过启动参数或环境变量明确提供配置文件路径:
APP_CONFIG_PATH=/data/workorder/application.toml
十六、键顺序和注释问题
toml::Table 默认按照键的字典顺序存储;启用 preserve_order feature 后,可以保留输入中的项目顺序。(Docs.rs)
配置:
toml = {
version = "1",
features = ["preserve_order"],
}
但需要特别注意:
toml::from_str()
↓
Rust 结构体或 toml::Table
↓
toml::to_string_pretty()
这个过程主要关注“数据”,不适合要求完整保留原配置文件的:
- 注释;
- 空行;
- 原始缩进;
- 原始引号风格;
- 所有排版细节。
例如原文件:
# 服务端配置 [server] # HTTP 端口 port = 5800
解析后再序列化,注释通常不会保留下来。
需要修改 TOML,同时保留注释、空格和项目相对顺序时,应使用同一项目提供的 toml_edit 库。toml_edit 的定位就是格式保留型 TOML 编辑。(Docs.rs)
简单区分:
读取应用配置、映射结构体
→ toml
修改 Cargo.toml,并保留注释和格式
→ toml_edit
十七、toml::Table 和结构体该怎么选
使用结构体
适用于:
application.toml database.toml server.toml 系统启动配置 数据库配置 Redis 配置 JWT 配置 业务功能开关
推荐写法:
let config: AppConfig =
toml::from_str(&content)?;
这是正式项目的首选。
使用 toml::Table
适用于:
配置结构不确定 插件自定义配置 动态键值配置 临时读取字段 配置检查工具 通用 TOML 查看器
推荐写法:
let table: toml::Table =
content.parse()?;
使用 toml::Value
适用于:
字段类型不确定 数组允许多种类型 需要递归遍历全部 TOML 内容 开发通用解析工具
十八、推荐的项目目录
结合 Rust Web 项目,可以这样组织:
workorder-server/
├── Cargo.toml
├── config/
│ ├── application.toml
│ ├── application-dev.toml
│ ├── application-test.toml
│ └── application-prod.toml
└── src/
├── main.rs
├── lib.rs
└── config/
├── mod.rs
├── model.rs
├── error.rs
└── loader.rs
不使用 mod.rs 的 Rust 2024 风格也可以写成:
src/
├── main.rs
├── config.rs
└── config/
├── model.rs
├── error.rs
└── loader.rs
src/config.rs:
pub mod error; pub mod loader; pub mod model; pub use error::ConfigError; pub use loader::load_config; pub use model::AppConfig;
十九、核心 API 总结
解析成结构体:
let config: AppConfig =
toml::from_str(&content)?;
解析成动态表:
let table: toml::Table =
content.parse()?;
序列化:
let content =
toml::to_string(&config)?;
格式化序列化:
let content =
toml::to_string_pretty(&config)?;
读取文件:
let content =
std::fs::read_to_string(path)?;
写入文件:
std::fs::write(path, content)?;
读取动态字段:
table
.get("server")
.and_then(toml::Value::as_table)
读取字符串:
value.as_str()
读取整数:
value.as_integer()
读取布尔值:
value.as_bool()
修改表:
value.as_table_mut()
二十、实际项目推荐方案
对于你的 Rust 2024、Salvo、SeaORM、MySQL 8、Redis 6 项目,推荐采用:
TOML
↓
std::fs::read_to_string
↓
toml::from_str
↓
强类型 AppConfig
↓
环境变量覆盖敏感配置
↓
启动前校验配置
↓
将 AppConfig 注入 Salvo 应用状态
关键原则是:
固定配置结构 → Rust 结构体 可选配置 → Option<T> 有默认值配置 → #[serde(default)] 防止拼写错误 → #[serde(deny_unknown_fields)] 动态服务名称 → HashMap<String, T> 多个同类配置 → Vec<T> + [[table]] 敏感配置 → 环境变量 保留注释修改 → toml_edit
其中最重要的一行代码就是:
let config: AppConfig =
toml::from_str(&content)?;
它负责把整个 TOML 配置文件安全地转换成强类型 Rust 配置对象。