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

Photo by Maciej Kraus and shared under the Creative Commons Attribution 2.0 Generic License. See https://www.flickr.com/photos/138892959@N03/25331206926/

Napisane 4 grudnia 2016