目录
- 1. dotenvy 是什么?
- 2. 为什么需要 dotenvy?
- 3. 安装 dotenvy
- 4. 最小可运行示例
- 5. dotenvy::dotenv().ok() 的常见写法
- 6. 读取并解析配置类型
- 7. 封装成 AppConfig 配置结构体
- 8. dotenvy::var 和 std::env::var 的区别
- 9. 加载指定文件:from_filename
- 10. 根据 APP_ENV 加载不同配置文件
- 11. 覆盖已有变量:dotenv_override / from_filename_override
- 12. 加载指定路径:from_path
- 13. 只遍历 .env,不写入环境变量:dotenv_iter
- 14. 与 SeaORM 结合示例
- 15. 与 Salvo Web 项目结合示例
- 16. .env 文件格式说明
- 17. .env.example 推荐写法
- 18. 推荐的项目目录结构
- 19. 常见坑
- 20. 实战推荐模板
- 21. 总结
下面以 Rust 2024 项目为例,详细讲 dotenvy 的作用、安装、常用 API 和实际项目用法。
1. dotenvy 是什么?
dotenvy 是 Rust 里用来读取 .env 文件的库。它会把 .env 文件里的配置项加载成环境变量,然后你就可以通过 std::env::var() 或 dotenvy::var() 读取。
官方文档说明:dotenvy 是 dotenv crate 的一个维护较好的 fork,主要用于从 .env 文件加载环境变量,方便开发环境使用。当前 docs.rs 上的稳定版本是 0.15.7。(文档.rs)
典型用途:
DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo REDIS_URL=redis://127.0.0.1:6379 SERVER_PORT=8080 JWT_SECRET=dev_secret_123
然后 Rust 代码里读取:
let database_url = std::env::var("DATABASE_URL")?;
2. 为什么需要 dotenvy?
不用 dotenvy 时,很多人可能会这样写:
let database_url = "mysql://root:123456@127.0.0.1:3306/fuyo";
这样有几个问题:
- 数据库密码写死在代码里,不安全。
- 开发环境、测试环境、生产环境配置不同,代码不好切换。
- 配置变更后要改代码重新编译。
- 容易把密码、Token、密钥提交到 Git 仓库。
使用 .env 后,代码只负责读取配置:
let database_url = std::env::var("DATABASE_URL")?;
真正的配置放在 .env 文件里:
DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo
3. 安装 dotenvy
方式一:Cargo.toml
[dependencies] dotenvy = "0.15"
方式二:命令安装
cargo add dotenvy
4. 最小可运行示例
项目结构:
dotenvy-demo/
├── Cargo.toml
├── .env
└── src/
└── main.rs
.env 文件:
APP_NAME=Fuyo-Workbench SERVER_HOST=127.0.0.1 SERVER_PORT=8080 DEBUG=true DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo
Cargo.toml:
[package] name = "dotenvy-demo" version = "0.1.0" edition = "2024" [dependencies] dotenvy = "0.15"
src/main.rs:
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 加载 .env 文件
// 如果当前目录找不到,会向父目录查找
dotenvy::dotenv()?;
let app_name = env::var("APP_NAME")?;
let server_host = env::var("SERVER_HOST")?;
let server_port = env::var("SERVER_PORT")?;
let debug = env::var("DEBUG")?;
let database_url = env::var("DATABASE_URL")?;
println!("APP_NAME = {}", app_name);
println!("SERVER_HOST = {}", server_host);
println!("SERVER_PORT = {}", server_port);
println!("DEBUG = {}", debug);
println!("DATABASE_URL = {}", database_url);
Ok(())
}
运行:
cargo run
输出类似:
APP_NAME = Fuyo-Workbench SERVER_HOST = 127.0.0.1 SERVER_PORT = 8080 DEBUG = true DATABASE_URL = mysql://root:123456@127.0.0.1:3306/fuyo
dotenvy::dotenv() 会从当前目录或父目录加载 .env 文件;如果系统环境变量里已经存在同名变量,默认会保留系统已有值,不覆盖它。(文档.rs)
5. dotenvy::dotenv().ok() 的常见写法
很多项目里你会看到:
dotenvy::dotenv().ok();
完整示例:
use std::env;
fn main() {
dotenvy::dotenv().ok();
let port = env::var("SERVER_PORT").unwrap_or_else(|_| "8080".to_string());
println!("Server port: {}", port);
}
这里的 .ok() 表示: 即使 .env 文件不存在,也不要让程序报错退出。
适合线上环境,因为线上一般不用 .env 文件,而是由 Docker、K8s、systemd、CI/CD 平台直接注入环境变量。
区别
严格要求 .env 必须存在:
dotenvy::dotenv()?;
允许 .env 不存在:
dotenvy::dotenv().ok();
开发阶段可以用第一种,生产项目入口通常更推荐第二种。
6. 读取并解析配置类型
环境变量读出来默认都是 String。如果你需要 u16、bool、usize,需要手动解析。
.env:
SERVER_PORT=8080 DEBUG=true MAX_CONNECTIONS=100
main.rs:
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let server_port: u16 = env::var("SERVER_PORT")?
.parse()?;
let debug: bool = env::var("DEBUG")?
.parse()?;
let max_connections: usize = env::var("MAX_CONNECTIONS")?
.parse()?;
println!("server_port = {}", server_port);
println!("debug = {}", debug);
println!("max_connections = {}", max_connections);
Ok(())
}
注意:
env::var("SERVER_PORT")?
得到的是字符串:
"8080"
再通过:
.parse::<u16>()?
转成数字。
7. 封装成 AppConfig 配置结构体
实际项目不建议到处写:
env::var("DATABASE_URL")?
更推荐统一封装一个配置结构体。
.env:
APP_NAME=Fuyo-Workbench SERVER_HOST=0.0.0.0 SERVER_PORT=8080 DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo REDIS_URL=redis://127.0.0.1:6379 JWT_SECRET=dev_jwt_secret
src/config.rs:
use std::env;
#[derive(Debug, Clone)]
pub struct AppConfig {
pub app_name: String,
pub server_host: String,
pub server_port: u16,
pub database_url: String,
pub redis_url: String,
pub jwt_secret: String,
}
impl AppConfig {
pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let app_name = env::var("APP_NAME")
.unwrap_or_else(|_| "rust-app".to_string());
let server_host = env::var("SERVER_HOST")
.unwrap_or_else(|_| "127.0.0.1".to_string());
let server_port = env::var("SERVER_PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse::<u16>()?;
let database_url = env::var("DATABASE_URL")?;
let redis_url = env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
let jwt_secret = env::var("JWT_SECRET")?;
Ok(Self {
app_name,
server_host,
server_port,
database_url,
redis_url,
jwt_secret,
})
}
pub fn server_addr(&self) -> String {
format!("{}:{}", self.server_host, self.server_port)
}
}
src/main.rs:
mod config;
use config::AppConfig;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = AppConfig::from_env()?;
println!("{:#?}", config);
println!("server addr = {}", config.server_addr());
Ok(())
}
这样做的好处:
- 配置集中管理。
- 默认值集中处理。
- 类型转换集中处理。
- 业务代码不用关心环境变量细节。
8. dotenvy::var 和 std::env::var 的区别
dotenvy 也提供了:
dotenvy::var("APP_NAME")?
官方文档说明,dotenvy::var() 用于获取环境变量值,只要是当前进程可见的环境变量都可以读取,不限于 .env 文件加载出来的变量。(文档.rs)
示例:
fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let app_name = dotenvy::var("APP_NAME")?;
println!("app_name = {}", app_name);
Ok(())
}
它和下面这个效果很接近:
std::env::var("APP_NAME")?
实际项目里,我更推荐:
dotenvy::dotenv().ok();
let app_name = std::env::var("APP_NAME")?;
因为 std::env::var() 是标准库 API,语义更清晰: dotenvy 负责加载,std::env 负责读取。
9. 加载指定文件:from_filename
默认情况下:
dotenvy::dotenv()?;
加载的是 .env。
如果你想加载指定文件,比如:
.env.dev .env.test .env.prod
可以用:
dotenvy::from_filename(".env.dev")?;
官方文档说明,from_filename 用于从指定文件加载环境变量;如果已有同名环境变量,默认仍然保留已有变量,不覆盖。(文档.rs)
示例:
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::from_filename(".env.dev")?;
let database_url = env::var("DATABASE_URL")?;
println!("DATABASE_URL = {}", database_url);
Ok(())
}
.env.dev:
DATABASE_URL=mysql://root:dev123@127.0.0.1:3306/fuyo_dev
.env.prod:
DATABASE_URL=mysql://root:prod123@10.10.16.186:3306/fuyo_prod
10. 根据 APP_ENV 加载不同配置文件
常见做法:
APP_ENV=dev
然后根据 APP_ENV 加载不同文件。
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 先尝试加载基础 .env
dotenvy::dotenv().ok();
let app_env = env::var("APP_ENV")
.unwrap_or_else(|_| "dev".to_string());
let env_file = match app_env.as_str() {
"dev" => ".env.dev",
"test" => ".env.test",
"prod" => ".env.prod",
_ => ".env.dev",
};
dotenvy::from_filename(env_file).ok();
let database_url = env::var("DATABASE_URL")?;
println!("APP_ENV = {}", app_env);
println!("DATABASE_URL = {}", database_url);
Ok(())
}
不过要注意:dotenvy::dotenv() 和 from_filename() 默认不会覆盖已有环境变量。如果 .env 里已经加载了 DATABASE_URL,后面 .env.dev 的同名变量可能不会覆盖它。
这时要用 override 版本。
11. 覆盖已有变量:dotenv_override / from_filename_override
默认行为:
dotenvy::dotenv()?;
不会覆盖已有环境变量。
如果你明确希望 .env 文件覆盖当前环境变量,可以用:
dotenvy::dotenv_override()?;
或者指定文件:
dotenvy::from_filename_override(".env.dev")?;
官方文档里也说明了,dotenv_override / from_filename_override 这类 API 会覆盖已有同名变量。(文档.rs)
示例:
.env:
DATABASE_URL=mysql://root:base@127.0.0.1:3306/base_db
.env.dev:
DATABASE_URL=mysql://root:dev@127.0.0.1:3306/dev_db
代码:
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
println!("first = {}", env::var("DATABASE_URL")?);
dotenvy::from_filename_override(".env.dev")?;
println!("after override = {}", env::var("DATABASE_URL")?);
Ok(())
}
输出:
first = mysql://root:base@127.0.0.1:3306/base_db after override = mysql://root:dev@127.0.0.1:3306/dev_db
12. 加载指定路径:from_path
如果 .env 不在项目根目录,可以用 from_path。
官方文档说明,from_path 可以从指定路径加载环境变量。(文档.rs)
示例目录:
project/
├── config/
│ └── local.env
└── src/
└── main.rs
config/local.env:
SERVER_PORT=9000
代码:
use std::env;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::from_path(Path::new("config/local.env"))?;
let port = env::var("SERVER_PORT")?;
println!("SERVER_PORT = {}", port);
Ok(())
}
13. 只遍历 .env,不写入环境变量:dotenv_iter
有时候你只是想读取 .env 内容,但不想真正写入环境变量,可以用:
dotenvy::dotenv_iter()?
官方文档说明,dotenv_iter() 会返回 .env 里的环境变量迭代器。(文档.rs)
示例:
fn main() -> Result<(), Box<dyn std::error::Error>> {
for item in dotenvy::dotenv_iter()? {
let (key, value) = item?;
println!("{}={}", key, value);
}
Ok(())
}
如果 .env 是:
APP_NAME=Fuyo SERVER_PORT=8080
输出:
APP_NAME=Fuyo SERVER_PORT=8080
这个适合做配置检查工具,比如启动前打印当前配置项,但注意不要打印密码、Token、JWT_SECRET。
14. 与 SeaORM 结合示例
你之前经常用 Rust + SeaORM + MySQL,这里给一个实际例子。
.env:
DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo_workbench
Cargo.toml 示例:
[dependencies]
dotenvy = "0.15"
sea-orm = { version = "1", features = ["sqlx-mysql", "runtime-tokio-rustls", "macros"] }
tokio = { version = "1", features = ["full"] }
main.rs:
use sea_orm::Database;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let database_url = env::var("DATABASE_URL")?;
let db = Database::connect(&database_url).await?;
println!("Database connected successfully!");
Ok(())
}
实际项目中可以放到 config.rs:
use std::env;
#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub url: String,
}
impl DatabaseConfig {
pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
let url = env::var("DATABASE_URL")?;
Ok(Self { url })
}
}
然后在启动时:
dotenvy::dotenv().ok(); let database_config = DatabaseConfig::from_env()?; let db = sea_orm::Database::connect(&database_config.url).await?;
15. 与 Salvo Web 项目结合示例
.env:
SERVER_HOST=0.0.0.0 SERVER_PORT=5800 DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo REDIS_URL=redis://127.0.0.1:6379 JWT_SECRET=dev_secret
Cargo.toml:
[dependencies]
dotenvy = "0.15"
salvo = "0.78"
tokio = { version = "1", features = ["full"] }
src/config.rs:
use std::env;
#[derive(Debug, Clone)]
pub struct AppConfig {
pub server_host: String,
pub server_port: u16,
pub database_url: String,
pub redis_url: String,
pub jwt_secret: String,
}
impl AppConfig {
pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
Ok(Self {
server_host: env::var("SERVER_HOST")
.unwrap_or_else(|_| "127.0.0.1".to_string()),
server_port: env::var("SERVER_PORT")
.unwrap_or_else(|_| "5800".to_string())
.parse::<u16>()?,
database_url: env::var("DATABASE_URL")?,
redis_url: env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string()),
jwt_secret: env::var("JWT_SECRET")?,
})
}
pub fn server_addr(&self) -> String {
format!("{}:{}", self.server_host, self.server_port)
}
}
src/main.rs:
mod config;
use config::AppConfig;
use salvo::prelude::*;
#[handler]
async fn hello() -> &'static str {
"Hello dotenvy + Salvo"
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = AppConfig::from_env()?;
let router = Router::new().get(hello);
println!("Server running at http://{}", config.server_addr());
let acceptor = TcpListener::new(config.server_addr()).bind().await;
Server::new(acceptor).serve(router).await;
Ok(())
}
16. .env 文件格式说明
常见写法:
APP_NAME=Fuyo SERVER_PORT=8080 DEBUG=true DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo
支持字符串:
APP_NAME="Fuyo Workbench"
支持带空格的值:
WELCOME_MESSAGE="hello rust dotenvy"
支持注释:
# 服务端口 SERVER_PORT=8080
不建议这样写:
SERVER_PORT = 8080
更推荐:
SERVER_PORT=8080
17. .env.example 推荐写法
真实 .env 不要提交到 Git。
.gitignore:
.env .env.local .env.dev .env.prod
但是可以提交 .env.example:
APP_NAME=your_app_name SERVER_HOST=127.0.0.1 SERVER_PORT=8080 DATABASE_URL=mysql://user:password@host:3306/database REDIS_URL=redis://127.0.0.1:6379 JWT_SECRET=change_me
团队成员拿到代码后:
cp .env.example .env
然后自己修改本地配置。
18. 推荐的项目目录结构
例如你的后端项目可以这样放:
workbench-backend/
├── Cargo.toml
├── .env
├── .env.example
└── src/
├── main.rs
├── config.rs
├── app/
├── entity/
├── service/
├── repository/
└── handler/
或者多环境:
workbench-backend/ ├── .env ├── .env.dev ├── .env.test ├── .env.prod ├── .env.example └── src/
我的建议是:
开发阶段:
.env
测试环境:
.env.test
线上环境:
不使用 .env,直接由系统环境变量注入
19. 常见坑
坑 1:.env 文件位置不对
dotenvy::dotenv() 会从当前目录或父目录找 .env 文件。(文档.rs)
如果你在项目根目录运行:
cargo run
通常没问题。
如果你在其他目录运行二进制,可能找不到 .env。
解决方式:
dotenvy::from_path("config/local.env")?;
坑 2:变量名写错
.env:
DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo
代码里写成:
env::var("DATABASEURL")?;
会报错。
建议统一大写加下划线:
DATABASE_URL REDIS_URL JWT_SECRET SERVER_PORT
坑 3:以为 .env 会覆盖系统环境变量
默认不会覆盖。
如果系统里已经有:
DATABASE_URL=mysql://prod
而 .env 里是:
DATABASE_URL=mysql://dev
那么:
dotenvy::dotenv()?;
会保留系统已有的 DATABASE_URL。官方文档明确说明,同名变量已经存在时,默认会保留已有值。(文档.rs)
如果你就是想覆盖:
dotenvy::dotenv_override()?;
或者:
dotenvy::from_filename_override(".env.dev")?;
坑 4:把 .env 提交到 Git
.env 里经常有:
DATABASE_URL=mysql://root:password@host:3306/db JWT_SECRET=xxx OSS_SECRET=xxx OPENAI_API_KEY=xxx
这些不要提交。
应该提交:
.env.example
不要提交:
.env
坑 5:生产环境过度依赖 .env
开发环境可以用:
dotenvy::dotenv().ok();
生产环境更推荐:
export DATABASE_URL=... export REDIS_URL=... export JWT_SECRET=...
或者通过 Docker Compose:
environment: DATABASE_URL: mysql://root:123456@mysql:3306/fuyo REDIS_URL: redis://redis:6379 JWT_SECRET: prod_secret
20. 实战推荐模板
你可以直接采用这个模板。
.env:
APP_NAME=Fuyo Workbench APP_ENV=dev SERVER_HOST=0.0.0.0 SERVER_PORT=5800 DATABASE_URL=mysql://root:123456@127.0.0.1:3306/fuyo_workbench REDIS_URL=redis://127.0.0.1:6379 JWT_SECRET=dev_secret_change_me
src/config.rs:
use std::env;
#[derive(Debug, Clone)]
pub struct AppConfig {
pub app_name: String,
pub app_env: String,
pub server_host: String,
pub server_port: u16,
pub database_url: String,
pub redis_url: String,
pub jwt_secret: String,
}
impl AppConfig {
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let config = Self {
app_name: env::var("APP_NAME")
.unwrap_or_else(|_| "rust-app".to_string()),
app_env: env::var("APP_ENV")
.unwrap_or_else(|_| "dev".to_string()),
server_host: env::var("SERVER_HOST")
.unwrap_or_else(|_| "127.0.0.1".to_string()),
server_port: env::var("SERVER_PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse::<u16>()?,
database_url: env::var("DATABASE_URL")?,
redis_url: env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string()),
jwt_secret: env::var("JWT_SECRET")?,
};
Ok(config)
}
pub fn server_addr(&self) -> String {
format!("{}:{}", self.server_host, self.server_port)
}
pub fn is_dev(&self) -> bool {
self.app_env == "dev"
}
pub fn is_prod(&self) -> bool {
self.app_env == "prod"
}
}
src/main.rs:
mod config;
use config::AppConfig;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = AppConfig::load()?;
println!("app_name = {}", config.app_name);
println!("app_env = {}", config.app_env);
println!("server_addr = {}", config.server_addr());
if config.is_dev() {
println!("当前是开发环境");
}
Ok(())
}
21. 总结
dotenvy 的核心作用就是:
dotenvy::dotenv().ok();
把 .env 文件加载进环境变量。
最常用读取方式:
let database_url = std::env::var("DATABASE_URL")?;
最常用项目写法:
pub struct AppConfig {
pub database_url: String,
pub redis_url: String,
pub server_port: u16,
}
开发环境用 .env,生产环境用系统环境变量,是比较稳妥的实践。