Rust: How to read environment variables

Mar 23, 2022

This is one of the examples I 😍 about this approach to learning a new systems language. It's already forcing me down the rabbit-hole of understanding so much about what makes Rust interesting. Here's the simplest possible way I could find to get the value of an environment var:

use std::env;

fn main() {
    let key = "HOME";
    let val = env::var(key).unwrap();
    println!("{}: {:?}", key, val); # /Users/glenngillen
}

Import the relevant stdlib, call the var function, all pretty standard so far. But there's that curious unwrap() call on there too. What's that about? Well it turns out most Rust functions return a Result type (or similar Option type) rather than just a value. Much like in golang (though there it's more of an idiom to return a tuple of values rather than a type of its own) you're expected to explicitly handle both the success and failure case and not just assume everything is always successful.

unwrap() is kinda cheating here, and asking Rust to just give me the value from the success result (i.e., "unwrap" the result type to give me the value). So what happens if we try to access an environment variable that is not set:

use std::env;

fn main() {
    let key = "NOPE";
    let val = env::var(key).unwrap(); # This will panic
    println!("{}: {:?}", key, val); # We'll never get here
}

we get this:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/playground`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: NotPresent', src/main.rs:15:29
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

ruh-roh! We can't have our whole app panic just because an environment variable is not set!

But before we get to that a brief diversion: while calling unwrap() in this particular example is poor form, there's plenty of scenarios where it's not just acceptable but would produce simpler and more maintainable code. So much so that there's an operator to make achieving a similar result easier: ?. Similar is doing a lot of heavy lifting here, as I couldn't just replace the use of unwrap() with ? in this example. You can only use ? on Result or Option types, and it also returns an Err automatically too. Which means the function calling it needs to be capable of returning an error result, which our current main() does not. Thankfully modern versions of Rust (anything >= 1.26.0) allow main() to return a result. So a quick update of our example to return a result, including the error type from a failed variable lookup:

use std::env;

fn main() -> Result<(), std::env::VarError> {
    let key = "NOPE";
    let val = env::var(key)?;
    println!("{}: {:?}", key, val);
    Ok(())
}

All that was just to highlight how to use ? instead of unwrap(). Given we're going to be handling the error case explicitly we'll revert back to the original main() definition for the rest of that. And with that, back to a more robust way of dealing with the scenario that an environment variable is not set.

Rust has the match keyword which can be used as a form of flow control like a switch statement. You can also use it as a way to handle the success or error cases for a result, with Ok() and Err() respectively:

use std::env;

fn main() {
    let key = "NONE";
    match env::var(key) {
        Ok(val) => println!("{}: {:?}", key, val),
        Err(e) => println!("couldn't interpret {}: {}", key, e),
    }
}

Here you can see that we're passing the Result from our call to env::var() into match. Then there's two branches. For the Ok() result, take the val and pass it into the println! call we had earlier. Now for the Err() case we catch the error raised (e) and print out more descriptive output of what went wrong. The result now is:

couldn't interpret NONE: environment variable not found
Hi, I'm Glenn! 👋 I'm currently Director of Product (Terraform) @ HashiCorp, and we're hiring! If you'd like to come and work with me and help make Terraform Cloud even more amazing we have multiple positions opening in Product ManagementDesign, and Engineering & Engineering Management across a range of levels (i.e., junior through to senior). Please send in an application ASAP so we can get in touch.