ExamplesBy LevelBy TopicLearning Paths
420 Fundamental

420: `env!` and `option_env!` — Compile-time Environment Variables

Functional Programming

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

  • • Understand how env! reads environment variables at compile time, not runtime
  • • Learn the standard Cargo-provided env vars: CARGO_PKG_VERSION, CARGO_PKG_NAME, CARGO_PKG_AUTHORS
  • • See how option_env! handles optional variables with Option<&'static str>
  • • Understand why compile-time embedding is preferable to runtime std::env::var for version info
  • • Learn how CI systems can inject build metadata via environment variables
  • Code 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")    // authors

    Key Differences

  • Built-in Cargo vars: Rust has documented, guaranteed CARGO_PKG_* variables; OCaml requires explicit dune configuration to expose package metadata.
  • Zero config: env!("CARGO_PKG_VERSION") requires no setup beyond having a Cargo.toml; OCaml's equivalent requires dune rules.
  • Optional: option_env! returns Option<&'static str> cleanly; OCaml's equivalent requires conditional compilation or runtime checks.
  • Build reproducibility: Compile-time env vars baked into the binary make builds non-reproducible if the env changes; both languages face this trade-off.
  • 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());
        }
    }
    ✓ Tests Rust test suite
    #[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.**
  • Fails compilation if variable is missing.
  • **option_env! returns Option<&str>.**
  • Cargo provides many built-in variables.
  • OCaml relies on runtime or build scripts.
  • Exercises

  • Version command: Create a 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.
  • Build-time API key: Use 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.
  • Custom build info: Write a 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.
  • Open Source Repos