不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-09 22:30:14  修改时间:2026-07-19 21:19:50  分类:01.Rust编程  编辑

目录

仓库地址:https://crates.io/crates/dotenvy

下面以 Rust 2024 项目为例,详细讲 dotenvy 的作用、安装、常用 API 和实际项目用法。

1. dotenvy 是什么?

dotenvy 是 Rust 里用来读取 .env 文件的库。它会把 .env 文件里的配置项加载成环境变量,然后你就可以通过 std::env::var()dotenvy::var() 读取。

官方文档说明:dotenvydotenv 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";

这样有几个问题:

  1. 数据库密码写死在代码里,不安全。
  2. 开发环境、测试环境、生产环境配置不同,代码不好切换。
  3. 配置变更后要改代码重新编译。
  4. 容易把密码、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。如果你需要 u16boolusize,需要手动解析。

.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(())
}

这样做的好处:

  1. 配置集中管理。
  2. 默认值集中处理。
  3. 类型转换集中处理。
  4. 业务代码不用关心环境变量细节。

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,生产环境用系统环境变量,是比较稳妥的实践。