Skip to main content

Module config_api

Module config_api 

Source
Expand description

Top-level document wrapper for reading and writing libconfig data.

triceratops logo

§Config API Documentation

The Config struct is the primary entry point for working with libconfig data. It wraps the root Value::Group of a configuration document. Per the grammar (<configuration> ::= <setting-list> | <empty>), the top level of a document is a list of settings — braces delimit nested groups only.

§Overview

Config wraps a private root Value::Group. It derefs to Value, so all Value methods (indexing, lookup, set, remove, type checks) work directly on a Config; Config::into_value() recovers ownership of the root, and Config::try_from(value) wraps an existing group (non-groups are rejected).

Config implements Serialize and Deserialize, so it can itself be used as a serde target: libconfig::from_str::<Config>(...) succeeds only for documents whose root is a group.

§Creating a Config

§Empty Config

use libconfig::Config;

let mut config = Config::new();
config.set("name", "My App").unwrap();
config.set("version", 1).unwrap();
config.set("debug", false).unwrap();

assert_eq!(config["name"], "My App");
assert_eq!(config["version"], 1);

Config::new() creates an empty configuration group. set accepts anything convertible into a Value and validates the setting name.

§Parsing a String

use libconfig::Config;

// A configuration is a list of settings at the top level
let config = Config::from_str("name = \"My App\";\nversion = 1;").unwrap();
assert_eq!(config["name"], "My App");

// Braces around the top level are not part of the grammar
// (<configuration> ::= <setting-list> | <empty>) and are rejected;
// braces delimit nested groups only
assert!(Config::from_str("{ name = \"My App\"; }").is_err());

§Parse Errors

Errors carry the failure position (1-based) and a typed kind:

use libconfig::{Config, ErrorKind};

let err = Config::from_str("a = 1;\nb = @;\n").unwrap_err();
assert_eq!(err.line(), Some(2));
assert_eq!(err.column(), Some(5));
assert_eq!(err.to_string(), "syntax error at line 2, column 5");
assert!(matches!(err.kind(), ErrorKind::Syntax));

§Reading a File

use libconfig::Config;

let config = Config::from_file("settings.cfg").unwrap();
println!("{}", config["database.host"]);

Config::from_file reads the file and parses it with Config::from_str. The inverse, Config::to_file(path), serializes the document and writes it (with a trailing newline) to the given path:

use libconfig::Config;

let mut config = Config::new();
config.set("name", "svc").unwrap();
config.to_file("settings.cfg").unwrap();

§Include Directives

Config::from_str and Config::from_file process @include directives per section 2.11 of the libconfig manual: each directive is replaced by the contents of the named file before parsing.

# file: test.cfg
info: {
  name = "Winston Churchill";
  @include "quote.cfg"
  country = "UK";
};

Rules, matching the C library:

  • A directive must appear on its own line: @include "path" (trailing comments are allowed). Directives inside /* ... */ comments are ignored.
  • Backslashes and double quotes in the path are escaped as \\ and \".
  • Includes may be nested at most 10 levels deep; exceeding the limit produces ErrorKind::Include.
  • Relative paths are resolved against the current working directory by default. To resolve them against a specific directory instead (the equivalent of config_set_include_dir()), use Config::from_str_with_include_dir / Config::from_file_with_include_dir:
use libconfig::Config;

let config = Config::from_file_with_include_dir("test.cfg", "/etc/myapp").unwrap();

For full control (the equivalent of config_set_include_func()), a custom resolver maps each directive path to the ordered list of files to inline — enabling wildcard expansion, search paths, or variable substitution:

use libconfig::Config;

let config = Config::from_file_with_include_fn("test.cfg", |path| {
    // e.g. expand "conf.d/*.cfg" with your favorite glob crate
    Ok(vec![std::path::PathBuf::from(path)])
}).unwrap();

Files produced by the resolver are scanned for nested @include directives (resolved by the same function), up to the 10-level limit.

Note that the serde-level libconfig::from_str does not process @include directives; include handling is a Config feature.

§Wrapping an Existing Value

TryFrom<Value> wraps a group value as a document; non-group values are rejected, since a configuration is a group of settings:

use libconfig::{Config, Value};

let v = Value::from_str("name = \"test\";").unwrap();
let config = Config::try_from(v).unwrap();
assert_eq!(config["name"], "test");

assert!(Config::try_from(Value::Integer(42)).is_err());

§Default

use libconfig::Config;

let config = Config::default(); // Same as Config::new()
assert!(config.is_group());
assert_eq!(config.as_group().unwrap().len(), 0);

§Serialization

§Config::to_string

Serializes the config to a libconfig string. The top level is an implicit setting list; nested groups are brace-delimited:

use libconfig::Config;

let config = Config::from_str("name = \"test\";\nversion = 42;").unwrap();
let output = config.to_string().unwrap();
assert_eq!(output, "name = \"test\";\nversion = 42;");

§Write Options

WriteOptions mirrors the C writer’s formatting knobs (manual chapter 3). Every combination produces grammar-valid output; defaults keep this crate’s canonical style:

use libconfig::{Config, WriteOptions};

let config = Config::from_str("port = 80; db = { host = \"h\"; };").unwrap();

// The C library's cosmetic style: ':' for groups, '{' on its own line
let opts = WriteOptions::new()
    .colon_assignment_for_groups(true)
    .open_brace_on_separate_line(true);
let text = config.to_string_with_options(&opts).unwrap();
assert_eq!(text, "port = 80;\ndb :\n{\n  host = \"h\";\n};");

Available options: float_precision(n) (fixed digits; default is the shortest form that round-trips exactly), scientific_notation(bool), tab_width(n) (0 = tabs), semicolon_separators(bool), colon_assignment_for_groups(bool), colon_assignment_for_non_groups(bool), and open_brace_on_separate_line(bool). Config::to_file_with_options and crate-level to_string_with_options accept the same struct.

§Display

Config implements Display, so it can be used with format! and println!:

use libconfig::Config;

let config = Config::from_str("x = 10; y = 20;").unwrap();
println!("{}", config);
// x = 10;
// y = 20;

§Serde Serialize

Config implements Serialize. Per the configuration file grammar, the top level of a document is an implicit setting list, so serde serialization produces no outer braces (braces only delimit nested groups):

use libconfig::Config;

let config = Config::from_str("a = 1; b = 2;").unwrap();

// Serde serialization writes the grammar's implicit top-level format
let serde_output = libconfig::to_string(&config).unwrap();
assert_eq!(serde_output, "a = 1;\nb = 2;");

// Config::to_string produces the same implicit format
let format_output = config.to_string().unwrap();
assert!(!format_output.starts_with('{'));

§Reading Values

Since Config derefs to Value, all Value access methods work directly:

§Indexing

use libconfig::Config;

let config = Config::from_str("name = \"test\"; ports = [80, 443];").unwrap();

assert_eq!(config["name"], "test");
assert_eq!(config["ports"][0], 80);

§Path Lookup

use libconfig::Config;

let config = Config::from_str(r#"
    database = {
        host = "localhost";
        credentials = {
            username = "admin";
        };
    };
"#).unwrap();

assert_eq!(
    config.lookup("database.credentials.username").unwrap().as_str(),
    Some("admin")
);

// Bracket notation for array indices
assert_eq!(
    config.lookup("database.host").unwrap().as_str(),
    Some("localhost")
);

§Type Checks and Conversions

use libconfig::Config;

let config = Config::from_str("count = 42; name = \"test\";").unwrap();

assert!(config["count"].is_i32());
assert_eq!(config["count"].as_i32(), Some(42));
assert_eq!(config["name"].as_str(), Some("test"));

// Config itself is always a group
assert!(config.is_group());

§Writing Values

§Setting Values

set takes anything convertible into a Value; explicit Value variants are only needed for aggregates:

use libconfig::{Config, Value};

let mut config = Config::new();

// Scalars convert automatically
config.set("name", "My App").unwrap();
config.set("version", 1).unwrap();
config.set("ratio", 0.75).unwrap();
config.set("debug", true).unwrap();
config.set("big_number", 100000000000i64).unwrap();

// Arrays and lists are built from Values
config.set("ports", Value::Array(vec![80.into(), 443.into()])).unwrap();

// Intermediate groups are created automatically
config.set("database.host", "localhost").unwrap();
config.set("database.port", 5432).unwrap();

assert_eq!(config["database.host"], "localhost");
assert_eq!(config["database.port"], 5432);

set returns a Result: names are validated against the grammar ([A-Za-z*][-A-Za-z0-9_*]*) and traversal failures are typed:

use libconfig::{Config, ErrorKind};

let mut config = Config::new();
assert!(matches!(
    config.set("my key", 1).unwrap_err().kind(),
    ErrorKind::InvalidSettingName(_)
));
config.set("scalar", 5).unwrap();
assert!(matches!(
    config.set("scalar.child", 1).unwrap_err().kind(),
    ErrorKind::PathTraversal(_)
));

§Modifying Existing Values

use libconfig::{Config, Value};

let mut config = Config::from_str("name = \"old\"; version = 1;").unwrap();

// Overwrite via set
config.set("name", "new").unwrap();
assert_eq!(config["name"], "new");

// Overwrite via lookup_mut
*config.lookup_mut("version").unwrap() = Value::Integer(2);
assert_eq!(config["version"], 2);

§Removing Values

use libconfig::{Config, Value};

let mut config = Config::from_str("a = 1; b = 2; c = 3;").unwrap();

let removed = config.remove("b");
assert_eq!(removed, Some(Value::Integer(2)));
assert!(config.lookup("b").is_none());
assert_eq!(config["a"], 1);
assert_eq!(config["c"], 3);

§Round-Trip Preservation

Config preserves key ordering through round-trips:

use libconfig::Config;

let input = "zebra = 1;\napple = 2;\nmonkey = 3;";
let config = Config::from_str(input).unwrap();
let output = config.to_string().unwrap();
assert_eq!(output, input); // Exact match, order preserved

§Indentation

Nested groups are indented with 2 spaces per level by default (see WriteOptions::tab_width to change this):

use libconfig::Config;

let mut config = Config::new();
config.set("server.listen.host", "0.0.0.0").unwrap();
config.set("server.listen.port", 8080).unwrap();

let output = config.to_string().unwrap();
// server = {
//   listen = {
//     host = "0.0.0.0";
//     port = 8080;
//   };
// };

§Config vs Value

ConfigValue
PurposeDocument-level wrapperIndividual data values
RootAlways a group (setting list)Any value type
from_strRequires a document (settings)Also parses bare values
Serializationto_string / to_file (+ _with_options)libconfig::to_string(&value)
ConstructionConfig::new(), Config::from_str()Value::from(42), "x".into(), variants
File I/OConfig::from_file() + @includeNo

Use Config when working with complete configuration documents. Use Value variants when constructing individual values to pass to Config::set().