Environment Variables
Environment variables fill the same config root as files. They sit above files and below CLI arguments in the precedence. This page covers naming, nesting, and the small conveniences figue adds. Exact rules live in the Environment Variables reference.
Prefix and the __ separator
Set a prefix on the config root with args::env_prefix. figue then reads
PREFIX__FIELD, using a double underscore to descend into nested structs.
use facet:: Facet ;
use figue::{ self as args, builder, Driver , MockEnv };
# [ derive ( Facet , Debug )]
struct Args {
# [ facet ( args :: config , args :: env_prefix = "MYAPP" )]
config : AppConfig ,
}
# [ derive ( Facet , Debug )]
struct AppConfig {
# [ facet ( default = 8080 )]
port : u16 ,
database : Database ,
}
# [ derive ( Facet , Debug )]
struct Database {
# [ facet ( default = 30 )]
connection_timeout : u64 ,
}
let config = builder ::< Args >()
. unwrap ()
. env ( |env| env. source ( MockEnv :: from_pairs ([
( "MYAPP__PORT" , "9000" ),
( "MYAPP__DATABASE__CONNECTION_TIMEOUT" , "60" ),
])))
. build ();
let out = Driver :: new ( config). run (). into_result (). unwrap ();
assert_eq! ( out. value . config . port , 9000 );
assert_eq! ( out. value . config . database . connection_timeout , 60 ); Why double underscore? Field names can contain single underscores
(connection_timeout). __ unambiguously means "go one level deeper", so
MYAPP__DATABASE__CONNECTION_TIMEOUT is database.connection_timeout, not
database.connection.timeout.
The variable name is upper-cased by convention; matching against fields is case-insensitive and kebab-aware.
MockEnv is for tests. In production read the real environment:
let config = builder ::< Args >()
. unwrap ()
. env ( |env| env) // reads std::env
. build (); Standard aliases with env_alias
Sometimes you want to honor a conventional, unprefixed variable like
DATABASE_URL or PORT. Add one or more args::env_alias:
# [ derive ( Facet , Debug )]
struct AppConfig {
/// Also read from $DATABASE_URL
# [ facet ( args :: env_alias = "DATABASE_URL" )]
database_url : String ,
/// Read from $PORT or $HTTP_PORT
# [ facet ( args :: env_alias = "PORT" , args :: env_alias = "HTTP_PORT" )]
port : u16 ,
} The prefixed form (MYAPP__PORT) takes priority over aliases when both are set;
among aliases, the first one found wins.
Lists from one variable
A value containing commas becomes a list. Escape a literal comma with \,:
# [ derive ( Facet , Debug )]
struct AppConfig {
# [ facet ( default )]
allowed_hosts : Vec < String >,
}
// MYAPP__ALLOWED_HOSTS=a.com,b.com,c.com -> ["a.com", "b.com", "c.com"] This comma-splitting is an environment-only convenience; CLI lists use repeated flags instead.
Enums and booleans
Enum values use the (kebab-cased) variant name; booleans accept
true/false/1/0/yes/no/on/off:
MYAPP__STORAGE =memory
MYAPP__LOGGING__JSON =trueFor an enum struct variant, descend into it like any nested struct:
MYAPP__STORAGE__S3__BUCKET =my-dataAn unknown enum variant from the environment is a warning, not a hard error — the raw value is still passed through to deserialization, which produces the precise message.
Multiple roots, multiple prefixes
With more than one config root, each uses its own env_prefix, so they never
collide:
# [ derive ( Facet , Debug )]
struct Args {
# [ facet ( args :: config , args :: env_prefix = "BEE" )]
bee : BeeConfig ,
# [ facet ( args :: config , args :: env_prefix = "BEE_EVAL" )]
eval : EvalConfig ,
}
// BEE__PORT and BEE_EVAL__DATASET are independent Unknown variables
A misspelled prefixed variable (MYAPP__PrOT) is tracked as an unused key.
It's only turned into an error if you opt into .env(|e| e.strict()) — and even
then it surfaces through the driver alongside the config dump, so the user sees
exactly what was understood. See
Errors & Diagnostics.
Next: Layered Configuration — the builder, the driver, and how all these layers combine.