| Crates.io | typed-env |
| lib.rs | typed-env |
| version | 0.3.0 |
| created_at | 2025-06-17 06:05:24.393006+00 |
| updated_at | 2025-11-14 03:47:15.841529+00 |
| description | Describing the requirements of environment variables in a type-safe and ergonomic way |
| homepage | https://github.com/thautwarm/typed-env |
| repository | https://github.com/thautwarm/typed-env |
| max_upload_size | |
| id | 1715263 |
| size | 42,365 |
Describe the requirements of environment variables in a type-safe and ergonomic way.
LazyLock-like globals for easy access to environment variables.true, 1, yes, y, on, enabled (case insensitive) for true, and false, 0, no, n, off, disabled (case insensitive) for false.Add to your Cargo.toml:
[dependencies]
typed-env = "0.2"
use typed_env::{Envar, EnvarDef};
// Define typed environment variables
static PORT: Envar<u16> = Envar::on_demand("PORT", || EnvarDef::Default(8080));
static DEBUG: Envar<bool> = Envar::on_demand("DEBUG", || EnvarDef::Default(false));
static DATABASE_URL: Envar<String> = Envar::on_demand("DATABASE_URL", || EnvarDef::Unset);
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Use the values - they're parsed automatically
let port = PORT.value()?; // u16
let debug = DEBUG.value()?; // bool
let db_url = DATABASE_URL.value()?; // String
println!("Server running on port {} (debug: {})", port, debug);
println!("Database: {}", db_url);
Ok(())
}
Variables are parsed when first accessed and cached thereafter. If the environment variable is changed, the new value will be loaded on the next access.
static API_KEY: Envar<String> = Envar::on_demand("API_KEY", || EnvarDef::Unset);
// Parsed and cached on first call
let key1 = API_KEY.value()?;
// Returns cached value
let key2 = API_KEY.value()?;
// ...
unsafe { std::env::set_var("API_KEY", "1234567890"); }
// ...
let key3 = API_KEY.value()?; // Loads the new value
// ...
Variables are parsed once at startup and never re-read:
static MAX_CONNECTIONS: Envar<u32> = Envar::on_startup("MAX_CONNECTIONS", || EnvarDef::Default(100));
// Always returns the same value, even if env var changes
let max_conn = MAX_CONNECTIONS.value()?;
WARNING: Envar::on_startup does not load the environment variables at the actual startup time, but at the time of the first access.
All standard integer and float types are supported:
static TIMEOUT_MS: Envar<u64> = Envar::on_demand("TIMEOUT_MS", || EnvarDef::Default(5000));
static RATE_LIMIT: Envar<f64> = Envar::on_demand("RATE_LIMIT", || EnvarDef::Default(10.5));
Flexible boolean parsing with multiple accepted formats:
static ENABLE_LOGS: Envar<bool> = Envar::on_demand("ENABLE_LOGS", || EnvarDef::Default(true));
Accepted values:
true, 1, yes, y, on, enabled (case insensitive)false, 0, no, n, off, disabled (case insensitive)falseParse delimited lists with configurable separators and filtering:
use typed_env::{ListEnvar, ListEnvarConfig};
// Define a custom list configuration
struct CommaList;
impl ListEnvarConfig for CommaList {
const SEP: &'static str = ",";
const FILTER_EMPTY_STR: bool = true;
const FILTER_WHITESPACE: bool = true;
}
static ALLOWED_ORIGINS: Envar<ListEnvar<String, CommaList>> =
Envar::on_demand("ALLOWED_ORIGINS", || EnvarDef::Unset);
// ALLOWED_ORIGINS="localhost,127.0.0.1,example.com"
let origins = ALLOWED_ORIGINS.value()?;
for origin in origins.iter() {
println!("Allowed origin: {}", origin);
}
use typed_env::{
Envar,
EnvarDef,
EnvarError,
EnvarParse,
EnvarParser,
ErrorReason
};
// accept: LEVEL="vvv"
static LEVEL: Envar<Level> = Envar::on_demand("LEVEL", || EnvarDef::Unset);
#[derive(Clone, Debug)]
pub struct Level(pub usize);
impl EnvarParse<Level> for EnvarParser<Level> {
fn parse(varname: std::borrow::Cow<'static, str>, value: &str) -> Result<Level, typed_env::EnvarError> {
let value = value.trim();
let mut count = 0;
for c in value.chars() {
if c == 'v' {
count += 1;
}
else {
return Err(EnvarError::ParseError {
varname,
typename: std::any::type_name::<Level>(),
value: value.to_string(),
reason: ErrorReason::new(move || format!("invalid character: {}", c)),
});
}
}
Ok(Level(count))
}
}
Provide a fallback value when the environment variable is not set:
static WORKER_THREADS: Envar<usize> = Envar::on_demand("WORKER_THREADS", || EnvarDef::Default(4));
// Returns 4 if WORKER_THREADS is not set
let threads = WORKER_THREADS.value()?;
Require the environment variable to be present:
static SECRET_KEY: Envar<String> = Envar::on_demand("SECRET_KEY", || EnvarDef::Unset);
// Returns Err(EnvarError::NotSet) if SECRET_KEY is not set
let secret = SECRET_KEY.value()?;
The library provides detailed error information:
use typed_env::EnvarError;
match DATABASE_URL.value() {
Ok(url) => println!("Database URL: {}", url),
Err(EnvarError::NotSet(name)) => {
eprintln!("Required environment variable {} is not set", name);
}
Err(EnvarError::ParseError { varname, typename, value, .. }) => {
eprintln!("Failed to parse {} as {}: {:?}", varname, typename, value);
}
}
Envar<T>: The main environment variable containerEnvarDef<T>: Defines default behavior (Default(value) or Unset)ListEnvar<T, C>: Container for list-type environment variablesListEnvarConfig: Trait for configuring list parsing behaviorEnvar::on_demand(name, default_factory): Create an on-demand loaded variableEnvar::on_startup(name, default_factory): Create a startup-loaded variableenvar.value(): Get the parsed value, returns Result<T, EnvarError>envar.name(): Get the environment variable nameEnvarError::NotSet(name): Environment variable is not set and no default providedEnvarError::ParseError { varname, typename, value, reason }: Failed to parse the valueEnvarError::TryDefault(varname): A "soft" error, indicating that the environment variable could fallback to the default value, if specified with EnvarDef::Default.Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.