dynamic-config
Hot-reloadable, layered configuration for Rust — one attribute, lock-free reads.
The Book · API docs · Examples · Changelog
Configuration that stays live after startup: files, environment, remote stores and command-line flags merged into one typed struct, re-read when they change, served to every thread as one atomic load.
[]
= { = "0.6.2", = ["toml", "watch"] }
use dynamic_config;
use Deserialize;
use Duration;
The attribute declares — this type is a configuration — and generates its storage and accessors. The builder configures: where the sources are is runtime data, and it lives in runtime code.
Why this one
- Reads are lock-free and allocation-free.
current()acquires anarc-swapguard — 85 instructions, ~20 ns, and zero allocations per 100 000 reads, all three measured rather than asserted — so configuration can be read per request without a second thought. - A bad edit cannot take the process down. A file that no longer parses or validates degrades to "no change"; the previous snapshot keeps serving, and the error is reported.
- Layers with provenance.
defaults < discovered < files < remote < secrets_dir < .env < environment < bindings < flags < overrides— andsource_of("key")names the file, variable or store a value actually came from. - Secrets stay out of diagnostics. Errors, diffs, reports and
{:?}print paths and types, never values — enforced by its own test suite. - Any runtime, or none. The async surface is a
Futureand a thread; tokio, smol and Embassy all drive it. Blocking work never lands on your executor. - Remote stores are explicit.
refresh_remote()does the network round trip;load()never does. Eight store crates ship from dynamic-config-remote, each watching the way its protocol allows — seven over a network, and git.
The full story — precedence, profiles, discovery, hot reload, encryption, schema export, units, the last-known-good cache, testing patterns — lives in the book.
This repository, and the family
| Crate | What | Stability |
|---|---|---|
dynamic-config |
the engine: loading, layers, storage, watching | Beta |
dynamic-config-macros |
#[dynamic_config] |
Beta |
dynamic-config-embedded |
the same shape for no_std targets |
Beta |
dynamic-config-cli |
explain and diff on the command line — cargo install dynamic-config-cli |
Beta |
The rest of the family is released from its own repository, each naming this engine with a caret so a patch here reaches it without a release there:
| Repository | What it ships |
|---|---|
| dynamic-config-remote | eight store crates — etcd, Consul, NATS, Redis, Vault, S3, Firestore, git — and dynamic-config-server |
| dynamic-config-python | pip install dynamic-config-py; a dataclass, Pydantic or msgspec validates |
| dynamic-config-node | npm install dynamic-config-node; Zod, Ajv or a function of your own validates |
Every crate is Beta: breaking changes bump the minor pre-1.0 and are announced in the changelog; a patch never breaks.
Between here and 1.0, only security fixes and hotfixes land. The surface is what it is going to be for 0.x: no new sources, no new stores, no new methods on the settled types. Pin the minor version and take patches automatically. Details in Stability Tiers.
MSRV
| floor | |
|---|---|
dynamic-config core |
1.71 |
schema feature |
1.74 (schemars) |
watch / age / full features |
1.85 (measured, not declared) |
dynamic-config-cli |
1.85 |
dynamic-config-embedded |
1.83 |
The store crates, the server and the bindings declare their own floors, in their own repositories — a companion pays for what it pulls in.
MSRV changes are breaking. Every floor has a CI row against a real toolchain; the full table with reasons is in MSRV & Features.
Contributing
CONTRIBUTING.md is the short version; the onboarding tour walks every module. What will not be built, and why, is in Limitations & Not Planned; what might be is in ROADMAP.md.
Credits
What this engine is built on and whose ideas it took — CREDITS.md.
License
MIT.