420: `env!` and `option_env!` — Compile-time Environment Variables
Tutorial Video
Text description (accessibility)
This video demonstrates the "420: `env!` and `option_env!` — Compile-time Environment Variables" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Version numbers, API keys for build-time compilation, build metadata, and package names should be embedded in binaries without being hardcoded as string literals that can drift from the actual package metadata. Key difference from OCaml: 1. **Built
Tutorial
The Problem
Version numbers, API keys for build-time compilation, build metadata, and package names should be embedded in binaries without being hardcoded as string literals that can drift from the actual package metadata. env!("CARGO_PKG_VERSION") reads the version from Cargo.toml at compile time, producing a &'static str with zero runtime cost. option_env! handles optional variables that may not be present, returning Option<&'static str>. This is how --version flags get their version strings, and how build-time configuration is embedded into binaries.
env! is used by virtually every CLI tool for --version output, embedded firmware for build metadata, and any binary that needs to know its own version at runtime.
🎯 Learning Outcomes
env! reads environment variables at compile time, not runtimeCARGO_PKG_VERSION, CARGO_PKG_NAME, CARGO_PKG_AUTHORSoption_env! handles optional variables with Option<&'static str>std::env::var for version infoCode Example
// Required: compile error if missing
const VERSION: &str = env!("CARGO_PKG_VERSION");
// Optional: returns Option<&str>
const API_KEY: Option<&str> = option_env!("API_KEY");
// Common Cargo variables
env!("CARGO_PKG_NAME") // package name
env!("CARGO_PKG_VERSION") // version
env!("CARGO_MANIFEST_DIR") // path to Cargo.toml
env!("CARGO_PKG_AUTHORS") // authorsKey Differences
CARGO_PKG_* variables; OCaml requires explicit dune configuration to expose package metadata.env!("CARGO_PKG_VERSION") requires no setup beyond having a Cargo.toml; OCaml's equivalent requires dune rules.option_env! returns Option<&'static str> cleanly; OCaml's equivalent requires conditional compilation or runtime checks.OCaml Approach
OCaml dune build system generates Version.ml files from opam metadata: (library (name mylib) (inline_tests) (preprocessor_deps ../CHANGES.md)). The %%VERSION%% substitution in dune files inserts the package version. OCaml's Sys.argv.(0) provides the binary name but not version. Libraries like build_info generate OCaml modules from build metadata. There is no direct equivalent of env! — all approaches require build system configuration.
Full Source
#![allow(clippy::all)]
//! env! and option_env! Macros
//!
//! Accessing environment variables at compile time.
/// Package version from Cargo.toml.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Package name from Cargo.toml.
pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
/// Authors from Cargo.toml.
pub const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
/// Get version string.
pub fn version() -> &'static str {
VERSION
}
/// Get full version string.
pub fn full_version() -> String {
format!("{} v{}", PKG_NAME, VERSION)
}
/// Optional compile-time env var.
pub fn build_profile() -> &'static str {
option_env!("PROFILE").unwrap_or("unknown")
}
/// Check if debug build.
pub fn is_debug_build() -> bool {
cfg!(debug_assertions)
}
/// Get optional feature flag.
pub fn optional_api_key() -> Option<&'static str> {
option_env!("API_KEY")
}
/// Build metadata.
pub struct BuildInfo {
pub version: &'static str,
pub name: &'static str,
pub target: &'static str,
}
impl BuildInfo {
pub const fn new() -> Self {
BuildInfo {
version: env!("CARGO_PKG_VERSION"),
name: env!("CARGO_PKG_NAME"),
target: match option_env!("CARGO_CFG_TARGET_ARCH") {
Some(v) => v,
None => "unknown",
},
}
}
}
impl Default for BuildInfo {
fn default() -> Self {
Self::new()
}
}
/// Compile-time manifest directory.
pub fn manifest_dir() -> &'static str {
env!("CARGO_MANIFEST_DIR")
}
/// Include a file relative to manifest.
#[macro_export]
macro_rules! include_asset {
($path:literal) => {
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path))
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_not_empty() {
assert!(!VERSION.is_empty());
}
#[test]
fn test_pkg_name() {
assert_eq!(PKG_NAME, "example-420-macro-env");
}
#[test]
fn test_full_version() {
let fv = full_version();
assert!(fv.contains(PKG_NAME));
assert!(fv.contains(VERSION));
}
#[test]
fn test_build_info() {
let info = BuildInfo::new();
assert_eq!(info.version, VERSION);
assert_eq!(info.name, PKG_NAME);
}
#[test]
fn test_manifest_dir() {
let dir = manifest_dir();
assert!(!dir.is_empty());
}
#[test]
fn test_option_env_missing() {
let val = option_env!("VERY_UNLIKELY_ENV_VAR_12345");
assert!(val.is_none());
}
#[test]
fn test_option_env_present() {
let val = option_env!("CARGO_PKG_NAME");
assert!(val.is_some());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_not_empty() {
assert!(!VERSION.is_empty());
}
#[test]
fn test_pkg_name() {
assert_eq!(PKG_NAME, "example-420-macro-env");
}
#[test]
fn test_full_version() {
let fv = full_version();
assert!(fv.contains(PKG_NAME));
assert!(fv.contains(VERSION));
}
#[test]
fn test_build_info() {
let info = BuildInfo::new();
assert_eq!(info.version, VERSION);
assert_eq!(info.name, PKG_NAME);
}
#[test]
fn test_manifest_dir() {
let dir = manifest_dir();
assert!(!dir.is_empty());
}
#[test]
fn test_option_env_missing() {
let val = option_env!("VERY_UNLIKELY_ENV_VAR_12345");
assert!(val.is_none());
}
#[test]
fn test_option_env_present() {
let val = option_env!("CARGO_PKG_NAME");
assert!(val.is_some());
}
}
Deep Comparison
OCaml vs Rust: Compile-Time Environment
Rust env! and option_env!
// Required: compile error if missing
const VERSION: &str = env!("CARGO_PKG_VERSION");
// Optional: returns Option<&str>
const API_KEY: Option<&str> = option_env!("API_KEY");
// Common Cargo variables
env!("CARGO_PKG_NAME") // package name
env!("CARGO_PKG_VERSION") // version
env!("CARGO_MANIFEST_DIR") // path to Cargo.toml
env!("CARGO_PKG_AUTHORS") // authors
OCaml Equivalent
(* No direct compile-time env access *)
(* Use dune substitution or build scripts *)
(* dune file:
(library
(name mylib)
(preprocess (action (run %{bin:ppx_version} %{env:VERSION=0.0.0}))))
*)
(* Or runtime: *)
let version = Sys.getenv_opt "VERSION" |> Option.value ~default:"unknown"
5 Takeaways
env! reads env vars at compile time.**option_env! returns Option<&str>.**Exercises
print_version() function that prints "{PKG_NAME} {VERSION}\nBuilt by: {AUTHORS}\nProfile: {PROFILE}" using all available Cargo env vars. Call it from a main.rs with --version flag handling.option_env!("SENTRY_DSN") to conditionally initialize error reporting. If the key is present at compile time, initialize a mock Sentry client; otherwise print a warning that error reporting is disabled.build.rs script that sets cargo:rustc-env=BUILD_TIMESTAMP={timestamp} using SystemTime::now(). Access it with env!("BUILD_TIMESTAMP") in the library and verify the timestamp is baked in.