econf loads strongly typed configuration values from environment variables or from files referenced by environment variables. This repository contains three sibling implementations with one shared behavior spec:
go/: Go modulerust/: Rust workspace with runtime crate and derive macrozig/: Zig packagespec/: shared behavior and conformance scenarios
For a config type and field, econf builds an environment variable name by converting both names to uppercase snake case and joining them with _.
MyConfig.Host -> MY_CONFIG_HOST
MyConfig.KeyListNUM -> MY_CONFIG_KEY_LIST_NUM
Each field may be loaded from either:
MY_CONFIG_HOST=value
MY_CONFIG_HOST_FILE=/path/to/file-containing-value
When both are present, *_FILE wins. File contents are trimmed before parsing. Missing or empty values leave the field unchanged. Fields whose names start with _ are skipped.
Supported baseline type families are strings, booleans, signed integers, floats, and slices/vectors of those scalar types. Unsigned integers are intentionally outside the shared baseline.
import econf "github.com/liuchong/econf/go"
type MyConfig struct {
Host string
Port int
Enabled bool
Names []string
_secret string
}
func main() {
cfg := &MyConfig{}
econf.SetFields(cfg)
}Go parsing errors panic, matching the original package behavior. See go/README.md.
use econf::{load, Econf};
#[derive(Default, Econf)]
struct MyConfig {
host: String,
port: i32,
enabled: bool,
names: Vec<String>,
_secret: String,
}
fn main() -> econf::Result<()> {
let mut cfg = MyConfig::default();
load(&mut cfg)?;
Ok(())
}Rust returns Result for parse and file errors. See rust/README.md.
const std = @import("std");
const econf = @import("econf");
const MyConfig = struct {
host: []const u8 = "",
port: i32 = 0,
enabled: bool = false,
names: []const []const u8 = &.{},
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const allocator = arena.allocator();
var cfg = MyConfig{};
try econf.load(&cfg, allocator, .{});
}Zig returns errors and uses the caller's allocator for loaded strings and slices. See zig/README.md.
All implementations cover the same real-world scenarios: scalar fields, file override, string/int/float slices, skipped _ fields, custom separators, and parse errors.
Run the full suite:
./scripts/conformance.shSee spec/conformance.md for the scenario-to-test mapping.