Skip to content

Rust

To use varlock with Rust, install the standalone binary and run your app under varlock run — it loads and validates your env, then injects it into the process.

Add @generateRustEnv to your schema to generate a small, self-contained module — a serde-derived Env struct, a load() function that parses the injected __VARLOCK_ENV blob, and a SENSITIVE_KEYS constant.

.env.schema
# @generateRustEnv(path=src/env.rs)

The file is regenerated automatically on varlock load and varlock run, or explicitly with varlock codegen. It needs serde and serde_json:

Terminal window
cargo add serde --features derive
cargo add serde_json

Call load() once, then read fields off the struct — required fields are the plain type, optional ones are Option<T>:

mod env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let e = env::load()?; // call once, hold or pass `e` around
let host = e.db_host; // String (required field)
if let Some(port) = e.db_port { // Option<i64> (optional field)
println!("{host}:{port}");
}
Ok(())
}
Terminal window
varlock run -- cargo run

load() returns a clear error if __VARLOCK_ENV is missing (e.g. you forgot varlock run) or if a required key is absent. The struct derives a redacting Debug — sensitive fields print as <redacted>, so {:?} won’t leak secrets.

Every load() call re-parses the blob. If you want a process-wide instance, opt into a LazyLock yourself (loaded once, on first access; LazyLock needs Rust 1.80+, or use once_cell::sync::Lazy on older toolchains):

config.rs
use std::sync::LazyLock;
use crate::env;
pub static ENV: LazyLock<env::Env> =
LazyLock::new(|| env::load().expect("failed to load env"));
anywhere.rs
use crate::config::ENV;
let port = ENV.db_port; // Option<i64>

Prefer no globals? Call env::load()? once in main and pass &Env through your app state — the type comes along either way.