Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ambient-config

Level: warn · Article: Dependencies

All dependencies get constructed in one place. One file. That’s it.

What it checks

std::env::var, env::var_os or env::vars outside main and outside functions and types that exist to read configuration (a name containing config, settings or env, such as load_config or Config::from_env).

Don’t

#![allow(unused)]
fn main() {
async fn start_server(app: App) -> Result<(), ServerError> {
    let port: u16 = std::env::var("PORT")?.parse()?;
    app.listen(port).await
}
}

The signature says this needs an App. It also needs PORT, and you find out when it is missing, at runtime, in the environment where it was not set.

Do

struct Config {
    db_url: DatabaseUrl,
    port: Port,
}

impl Config {
    fn from_env() -> Result<Self, ConfigError> {
        Ok(Self {
            db_url: DatabaseUrl::parse(std::env::var("DATABASE_URL")?)?,
            port: Port::parse(std::env::var("PORT")?)?,
        })
    }
}

async fn start_server(app: App, port: Port) -> Result<(), ServerError> {
    app.listen(port).await
}

fn main() {
    let config = Config::from_env().expect("configuration required for startup");
    start_server(App::new(), config.port);
}

Read once, at the top, parsed into types. Everything below takes what it needs as a parameter, and a missing variable fails at startup instead of on the first request that reaches that code path.

Silence it

#![allow(unused)]
fn main() {
// rabot: allow(ambient-config) RUST_LOG is the logger's own contract, read by the logging crate
}