Reading & writing
Reading from several sources has a natural answer: return the value that wins after the layers are combined. Writing has an extra question: which file should dotcfg change?
Read the final configuration
ConfigStack gives you familiar read methods. get() reads one value as a string,get_as() reads one value as a Rust type, and load() deserializes the complete resolved configuration into your struct.
let port: u16 = cfg.get_as("server.port")?;
let config: Option<AppConfig> = cfg.load()?;These methods read the result of the stack, not one particular file. That means a value may come from an earlier layer, a later layer, or the environment depending on what is configured.
Why writes need a target
Suppose server.port exists in both defaults and project. If you call set_val("server.port", 8080), ConfigStack cannot safely guess which file you intended to edit.
You choose the writable source with write_to():
let cfg = ConfigStack::new()
.layer("defaults", defaults)
.layer("project", project)
.write_to("project")
.build()?;
cfg.set_val("server.port", 8080u16)?;In this example, stack-level writes go only to project. The defaults source is left untouched.
save() does not copy the whole stack
save() writes the struct you give it to the selected write target. It does not take values from the other layers or from environment variables and copy them into that file.
This matters because the layers still own their own data. A system defaults file should not suddenly be copied into a project file just because you saved project settings.
Work with one source directly
Sometimes you do not want the final combined value. You may want to inspect the defaults source itself, or intentionally change one particular source. Use source() for that.
let defaults = cfg.source("defaults")?;
let port: u16 = defaults.get_as("server.port")?;source() returns that source's DotCfg, so its normal DotCfg behavior applies. The other layers and the stack's environment prefix are not part of this direct read.
Errors that protect the stack
Layer names must be unique. build() returns Error::DuplicateSourceif the same name is added twice. If write_to() names a source that was never added, it returns Error::UnknownSource. These checks happen when you build the stack so configuration mistakes are caught before the stack is used.
Using Tokio? See async ConfigStack for the corresponding layered methods.