Rust中std::env::vars()传入函数后为空的原因排查
问题:环境变量传入函数后无法读取
需求:将环境变量传入函数
我写了一个接收std::env::vars()返回的Vars对象,并用它生成数据库配置对象的函数,代码如下:
pub fn config(mut vars: Vars) -> Config { let mut result: Config = Config::new(); result.dbname = vars.find(|kv| kv.0.eq("DB_NAME")) .map(|kv| kv.1); result.host = vars.find(|kv| kv.0.eq("DB_HOST")) .map(|kv| kv.1); println!("database host: {:?}", result.host); result.password = vars.find(|kv| kv.0.eq("DB_PASSWORD")) .map(|kv| kv.1); println!("database password: {:?}", result.password); println!("env password: {:?}" , vars.find(|kv| kv.0.eq("DB_PASSWORD")) .map(|kv| kv.1)); result.port = vars.find(|kv| kv.0.eq("DB_PORT")) .map(|kv| kv.1) .map(|string| string.parse::<u16>().ok()) .flatten(); println!("database port: {:?}", result.port); result.user = vars.find(|kv| kv.0.eq("DB_USER")) .map(|kv| kv.1); result.manager = Some(ManagerConfig { recycling_method: RecyclingMethod::Fast }); result }
问题:传入的Vars对象读取不到值
函数里所有打印语句都显示变量值为None,但在main函数里打印同一个Vars对象时,所有变量都能正常显示:
#[actix_web::main] async fn main() -> std::io::Result<()> { println!("vars:"); for (key, value) in std::env::vars() { println!(" {key}: {value}"); // 这里正常输出 } println!("env password: {:?}" , env::vars().find(|kv| kv.0.eq("DB_PASSWORD")) .map(|kv| kv.1)); let port = environment::port(env::var("PORT")); HttpServer::new(|| { App::new() .app_data( Data::new(environment::config(std::env::vars()) // 这里读取不到值 .create_pool(None, tokio_postgres::NoTls) .expect("Error initializing connection pool")) ) .route("/", get().to(index_controller::index)) .route("/username-available", post() .to(user_controller::username_available2)) .wrap(session::middleware()) }) .bind(("0.0.0.0", port))? .run() .await }
疑问:为什么main里能正常打印,函数里却不行?
程序输出显示main中能正确列出环境变量,但函数中获取的变量均为None,具体输出如下:
huntergatherer3-web-1 | vars: huntergatherer3-web-1 | PATH: /usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin huntergatherer3-web-1 | HOSTNAME: 22f204119a79 huntergatherer3-web-1 | DB_HOST: database huntergatherer3-web-1 | DB_NAME: huntergatherer huntergatherer3-web-1 | DB_PASSWORD: postgres_password huntergatherer3-web-1 | DB_PORT: 5432 huntergatherer3-web-1 | DB_USER: postgres_user huntergatherer3-web-1 | PORT: 8082 huntergatherer3-web-1 | RUST_LOG: debug huntergatherer3-web-1 | RUSTUP_HOME: /usr/local/rustup huntergatherer3-web-1 | CARGO_HOME: /usr/local/cargo huntergatherer3-web-1 | RUST_VERSION: 1.71.0 huntergatherer3-web-1 | HOME: /root huntergatherer3-web-1 | env password: Some("postgres_password") huntergatherer3-web-1 | Got port 8082. huntergatherer3-web-1 | database host: None huntergatherer3-web-1 | database password: None huntergatherer3-web-1 | env password: None huntergatherer3-web-1 | database port: None huntergatherer3-web-1 | database host: None huntergatherer3-web-1 | database password: None huntergatherer3-web-1 | env password: None huntergatherer3-web-1 | database port: None
原因分析
std::env::vars()返回的Vars是一次性迭代器,每次调用find方法都会消耗迭代器中的元素——要么找到目标值后停在对应位置,要么遍历完所有元素后走到迭代器末尾。后续的find调用会从当前位置继续向后查找,而环境变量的存储顺序不确定,如果第一次find就遍历到了迭代器末尾,所有后续查询都会直接返回None。
解决方案
1. 转换为可重复查询的集合
在函数内部把Vars转换成HashMap,这样可以多次查询任意键值:
use std::collections::HashMap; pub fn config(vars: Vars) -> Config { let env_map: HashMap<String, String> = vars.collect(); let mut result = Config::new(); result.dbname = env_map.get("DB_NAME").cloned(); result.host = env_map.get("DB_HOST").cloned(); println!("database host: {:?}", result.host); result.password = env_map.get("DB_PASSWORD").cloned(); println!("database password: {:?}", result.password); println!("env password: {:?}", env_map.get("DB_PASSWORD").cloned()); result.port = env_map.get("DB_PORT") .and_then(|s| s.parse::<u16>().ok()); println!("database port: {:?}", result.port); result.user = env_map.get("DB_USER").cloned(); result.manager = Some(ManagerConfig { recycling_method: RecyclingMethod::Fast }); result }
2. 每次查询生成新迭代器
直接在每个查询时调用std::env::vars()生成新迭代器,避免单次迭代器的消耗问题:
pub fn config() -> Config { // 无需传入Vars参数 let mut result = Config::new(); result.dbname = std::env::vars().find(|kv| kv.0.eq("DB_NAME")).map(|kv| kv.1); result.host = std::env::vars().find(|kv| kv.0.eq("DB_HOST")).map(|kv| kv.1); println!("database host: {:?}", result.host); result.password = std::env::vars().find(|kv| kv.0.eq("DB_PASSWORD")).map(|kv| kv.1); println!("database password: {:?}", result.password); println!("env password: {:?}", std::env::vars().find(|kv| kv.0.eq("DB_PASSWORD")).map(|kv| kv.1)); result.port = std::env::vars().find(|kv| kv.0.eq("DB_PORT")) .map(|kv| kv.1) .and_then(|s| s.parse::<u16>().ok()); println!("database port: {:?}", result.port); result.user = std::env::vars().find(|kv| kv.0.eq("DB_USER")).map(|kv| kv.1); result.manager = Some(ManagerConfig { recycling_method: RecyclingMethod::Fast }); result }
调用时直接写environment::config()即可,无需传入参数。
3. 单次遍历填充配置
只遍历一次环境变量,在遍历过程中填充Config的所有字段,避免多次迭代:
pub fn config(vars: Vars) -> Config { let mut result = Config::new(); for (key, value) in vars { match key.as_str() { "DB_NAME" => result.dbname = Some(value), "DB_HOST" => result.host = Some(value), "DB_PASSWORD" => result.password = Some(value), "DB_PORT" => result.port = value.parse::<u16>().ok(), "DB_USER" => result.user = Some(value), _ => {} // 忽略无关环境变量 } } println!("database host: {:?}", result.host); println!("database password: {:?}", result.password); println!("env password: {:?}", result.password); println!("database port: {:?}", result.port); result.manager = Some(ManagerConfig { recycling_method: RecyclingMethod::Fast }); result }
内容的提问来源于stack exchange,提问作者Eleanor Holley
相关产品推荐
相关产品推荐

