dynamic-config 0.8.0

Hot-reloadable, lock-free application configuration with a one-attribute API, built on figment.
Documentation

dynamic-config

Hot-reloadable, layered configuration for Rust — one attribute, lock-free reads.

CI Security crates.io docs.rs MSRV License: MIT OpenSSF Scorecard

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.

[dependencies]
dynamic-config = { version = "0.8.0", features = ["toml", "watch"] }
use dynamic_config::dynamic_config;
use serde::Deserialize;
use std::time::Duration;

#[dynamic_config]
#[derive(Debug, Deserialize)]
pub struct DatabaseConfig {
    pub host: String,
    pub port: u16,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let builder = DatabaseConfig::builder("db")
        .file("config.toml")
        .file("secrets.json")
        .env("APP_");

    builder.init()?;                                     // load once, fail fast on a bad config
    builder.watch(Duration::from_millis(250))?.detach(); // reload in the background from now on

    let config = DatabaseConfig::current();              // one atomic load, on any thread
    println!("{}:{}", config.host, config.port);

    Ok(())
}

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 an arc-swap guard — 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 — and source_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 Future and 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

1.88, one number for the whole organisation — core, every feature, the CLI and the embedded cell alike. The per-feature ladder collapsed in 0.8.0 as security work: three advisory fixes the old floors could not take are ordinary lockfile entries at 1.88, and older toolchains resolve the last pre-raise releases through the MSRV-aware resolver (EOL, per the Compatibility Contract).

MSRV changes are breaking and announced. The floor has CI rows against the real toolchain; the story with reasons — and what each feature weighs — 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.

What you may build on and find unchanged tomorrow is written down: the Compatibility Contract.

License

MIT.