24 days of Rust - environment variables
Environment variables are a set of dynamic named values that can affect the way running processes will behave on a computer.
That's Wikipedia. Let's read it again. Dynamic - because they can change. Named - because like any other variables, they have names. Affect processes - this is the most important part. Environment variables tell the program in what context it is running - what's the current language, where is user's home directory etc. They can also store configuration for the process. For example, a popular cloud hosting platform (Heroku) exposes configuration values to the app as environment variables.
Standard library
The simplest way to access environment variables from Rust is to use the
built-in std::env
module. It exposes
a few useful functions, such as vars()
which returns an iterator over
all environment variables. In the example below we're using var()
to read
current language setting.
use std::env; fn main() { match env::var("LANG") { Ok(lang) => println!("Language code: {}", lang), Err(e) => println!("Couldn't read LANG ({})", e), }; }
$ cargo run Language code: pl_PL.UTF-8
envy
envy
is a small crate that uses serde
to automatically deserialize process environment into a Rust struct.
#![feature(proc_macro)] extern crate envy; #[macro_use] extern crate serde_derive; #[derive(Deserialize, Debug)] struct Environment { lang: String, } match envy::from_env::<Environment>() { Ok(environment) => println!("Language code: {}", environment.lang), Err(e) => println!("Couldn't read LANG ({})", e), };
envy
normalizes variable names to lower case, so LANG
becomes lang
attribute of the struct. But because it's just serde
, we can use
serde attributes to
control renames. Another thing worth noting is serialize_with
attribute.
For example if we had a comma-separated list in an environment variable,
we could deserialize it to a Vec
with something similar to the code in this
issue on GitHub.
dotenv
Sometimes we don't want to export a lot of environment variables manually.
We could write a shell script that wraps our application... or we can use
dotenv
. Dotenv is a convention to put environment variables in a .env
file. There are libraries to read .env
in
Ruby,
Python,
PHP and a few other languages, but
here we're interested in the dotenv
crate.
The dotenv::dotenv()
function does one simple thing: it sets environment
variables based on .env
file contents. If we have a .env
file like this:
EMAIL_FROM=root@localhost EMAIL_BACKEND=smtp
We can now read these from our Rust program like any other environment variables.
extern crate dotenv; dotenv::dotenv().expect("Failed to read .env file"); println!("Email backend: {}", env::var("EMAIL_BACKEND").expect("EMAIL_BACKEND not found"));
Even better - we can combine dotenv
with envy
and read our configuration
stored in the .env
file into a Rust struct.
#[derive(Deserialize, Debug)] struct MailerConfig { email_backend: String, email_from: String, } dotenv::dotenv().expect("Failed to read .env file"); match envy::from_env::<MailerConfig>() { Ok(config) => println!("{:?}", config), Err(e) => println!("Couldn't read mailer config ({})", e), };
$ cargo run MailerConfig { email_backend: "smtp", email_from: "root@localhost" }
Further reading
- What are PATH and other environment variables, and how can I set or use them?
- The Twelve-factor app: store config in the environment
- Implementing Deserialize with serde
Photo by Maciej Kraus and shared under the Creative Commons Attribution 2.0 Generic License. See https://www.flickr.com/photos/138892959@N03/25331206926/