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/