Every Rust service I write ends up with a block of std::env::var("FOO").ok().and_then(...) chains that all look the same. A tiny macro_rules! cleans that up. It supports two forms: required (panics on startup with a clear message) and default (parses the env var if present, otherwise uses the default).

It is not magical. It is the same .parse::<T>()? you would have written, just without the boilerplate.

macro_rules! env_var {
    ($name:literal: $ty:ty) => {{
        let s = std::env::var($name)
            .unwrap_or_else(|_| panic!("missing env var {}", $name));
        s.parse::<$ty>()
            .unwrap_or_else(|e| panic!("bad env var {}: {}", $name, e))
    }};
    ($name:literal: $ty:ty = $default:expr) => {{
        match std::env::var($name) {
            Ok(s) => s.parse::<$ty>()
                .unwrap_or_else(|e| panic!("bad env var {}: {}", $name, e)),
            Err(_) => $default,
        }
    }};
}

fn main() {
    let port: u16       = env_var!("PORT": u16 = 8080);
    let max: usize      = env_var!("MAX_CONNS": usize);
    let name: String    = env_var!("SERVICE_NAME": String = "api".into());
    println!("{name} on :{port}, max {max}");
}

Caveats: I use this only at startup, so panics are fine. For anything runtime-reloadable, return a Result. See also /posts/result-and-option-ergonomics-after-six-months/.