Rust: How to read environment variables | Glenn Gillen
Rust: How to read environment variables

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've spent most of my career working with or at startups. I'm currently the Director of Product @ Ockam where I'm helping developers build applications and systems that are secure-by-design. It's time we started securely connecting apps, not networks.

Previously I led the Terraform product team @ HashiCorp, where we launched Terraform Cloud and set the stage for a successful IPO. Prior to that I was part of the Startup Team @ AWS, and earlier still an early employee @ Heroku. I've also invested in a couple of dozen early stage startups.