420: env! and option_env! for Build-time Values
Difficulty: 2 Level: Intermediate Embed environment variables directly into your binary at compile time โ no runtime config file, no `std::env::var`, just constants baked in during the build.The Problem This Solves
Applications often need values that are fixed for a given build: version strings, API endpoints, feature flags, build timestamps. Reading them at runtime from environment variables or config files adds startup complexity, failure modes ("where's the config?"), and potential security exposure. Including them at compile time means they're constants โ inlined, no parsing, no I/O, no missing values. `env!("VAR")` reads an environment variable at compile time and produces a `&'static str`. If the variable isn't set, the build fails โ not the runtime. This is a deliberate design: the problem is caught at the source. `option_env!("VAR")` returns `Option<&'static str>` for optional values, letting you handle absence gracefully at compile time. Combined with Cargo's automatic variables (`CARGO_PKG_VERSION`, `CARGO_PKG_NAME`, `CARGO_MANIFEST_DIR`), this covers the most common case โ version strings โ without any extra setup.The Intuition
`env!("VAR")` reads an environment variable when `cargo build` runs and bakes it into the binary as a compile-time constant โ the variable doesn't need to exist at runtime.How It Works in Rust
// Cargo automatically sets these โ always available
const VERSION: &str = env!("CARGO_PKG_VERSION");
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
// Build fails if MY_API_KEY isn't set during cargo build
const API_KEY: &str = env!("MY_API_KEY");
// option_env! โ None if not set, Some(&str) if set
const OPTIONAL_ENDPOINT: Option<&str> = option_env!("STAGING_URL");
fn main() {
println!("{} v{}", PKG_NAME, VERSION);
match OPTIONAL_ENDPOINT {
Some(url) => println!("Using staging: {}", url),
None => println!("Using production endpoint"),
}
}
// Common pattern: build info struct
pub struct BuildInfo {
pub version: &'static str,
pub pkg_name: &'static str,
pub profile: &'static str,
}
pub const BUILD: BuildInfo = BuildInfo {
version: env!("CARGO_PKG_VERSION"),
pkg_name: env!("CARGO_PKG_NAME"),
profile: if cfg!(debug_assertions) { "debug" } else { "release" },
};
1. `env!("NAME")` โ `&'static str` constant. Build error if unset.
2. `option_env!("NAME")` โ `Option<&'static str>`. `None` if unset, `Some(val)` if set.
3. Cargo pre-sets: `CARGO_PKG_VERSION`, `CARGO_PKG_NAME`, `CARGO_PKG_AUTHORS`, `CARGO_MANIFEST_DIR`.
4. For custom values, set the env var before `cargo build`, or use `build.rs` to emit `cargo:rustc-env=VAR=value`.
What This Unlocks
- Zero-overhead version strings: `env!("CARGO_PKG_VERSION")` baked in as a static string โ no parsing, no allocation.
- CI/CD build tagging: Inject git commit SHA, build timestamp, or CI pipeline ID at build time.
- Fail-fast secrets: Mandatory API keys cause build failure, not silent runtime misconfiguration.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Compile-time constants | `[%%getenv "VAR"]` (ppx_getenv) or custom PPX | `env!("VAR")` built-in |
| Package version | Manual or external tooling | `env!("CARGO_PKG_VERSION")` automatic |
| Optional build var | Custom handling required | `option_env!("VAR")` returns `Option` |
| Build script injection | Makefile / Dune env | `build.rs` emits `cargo:rustc-env=VAR=val` |
| Runtime env | `Sys.getenv` | `std::env::var("VAR")` (different from `env!`) |
//! 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());
}
}
(* Build-time values in OCaml via dune substitution *)
(* In dune, you'd use (subst) and write version strings *)
(* Here we simulate with constants *)
let version = "1.0.0" (* would come from %{version} in dune *)
let pkg_name = "my-app"
let build_date = "2026-01-01" (* would come from build system *)
let () =
Printf.printf "%s v%s\n" pkg_name version;
Printf.printf "Built: %s\n" build_date;
(* Runtime env var access *)
let home = try Sys.getenv "HOME" with Not_found -> "/unknown" in
Printf.printf "HOME: %s\n" home;
let debug = match Sys.getenv_opt "DEBUG" with
| Some "1" | Some "true" -> true
| _ -> false
in
Printf.printf "Debug mode: %b\n" debug