I have a function which takes the std::env::vars() object and returns a database configuration object, like so:
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
}
All of the above print statements show None
for the given variables.
However, I know those variables are being set because when I print from the same object in main, the variables all show up:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("vars:");
for (key, value) in std::env::vars() {
println!(" {key}: {value}"); //works as expected
}
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()) //does not work as expected
.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
}
The output of the program is, puzzlingly, as follows:
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
Vars
implements Iterator
. Advancing an iterator is a mutating operation; it consumes the item at the front of the iterator. In your function, you use .find()
repeatedly, which advances the iterator until the item is found, discarding the items that it skips.
In other words, unless Vars
produces the environment variables in exactly the same order your code searches for them you will skip over variables you want later and they will no longer be available.
If you want to take a Vars
as an argument, you could simply iterate over vars
and assign the results based on the current key, something like this:
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),
// and so on...
// Catch-all arm to discard irrelevant variables.
_ => {}
}
}
result
}
There are other approaches as well, such as collecting Vars
into a HashMap<String, String>
or using std::env::var()
to fetch each variable one at a time.