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.2.0", = ["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.
current()is an atomic pointer load — ~17 ns — 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 < .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. Seven store crates ship, each watching the way its protocol allows.
The full story — precedence, profiles, discovery, hot reload, encryption, schema export, units, the last-known-good cache, testing patterns — lives in the book.
The workspace
| Crate | What | Stability |
|---|---|---|
dynamic-config |
the engine: loading, layers, storage, watching | Beta |
dynamic-config-macros |
#[dynamic_config] |
Beta |
dynamic-config-etcd |
etcd, push watch over gRPC | Experimental |
dynamic-config-consul |
Consul KV, blocking queries | Experimental |
dynamic-config-nats |
NATS JetStream KV, push watch | Experimental |
dynamic-config-redis |
Redis, keyspace notifications | Experimental |
dynamic-config-vault |
Vault KV v2, version polling | Experimental |
dynamic-config-s3 |
S3 & compatibles, ETag polling — needs tokio | Experimental |
dynamic-config-firestore |
Firestore REST, updateTime polling |
Experimental |
dynamic-config-embedded |
the same shape for no_std targets |
Experimental |
dynamic-config-cli |
explain and diff on the command line — in-repo, not yet published |
Experimental |
Beta: breaking changes bump the minor pre-1.0 and are announced in the changelog. Experimental: may change shape without ceremony — pin an exact version. Details in Stability Tiers.
Every store follows the same contract — the current value is not announced at startup, a deleted key is not a change, transport failures retry, a panicking callback ends the watch with an error — and each documents its stop latency and change-detection rule side by side in Store Crates at a Glance.
MSRV
| floor | |
|---|---|
dynamic-config core |
1.71 |
schema feature |
1.74 (schemars) |
watch / age / full features |
1.85 (measured, not declared) |
| store crates | 1.85 — nats/redis/s3: 1.88 (their clients) |
dynamic-config-cli |
1.85 |
dynamic-config-embedded |
1.83 |
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.
License
MIT.