static_toml!() { /* proc-macro */ }Expand description
Macro to statically embed TOML files.
For basic usage and configuration, see the crate-level documentation.
§Macro Invocation Overview
Consider the following example where the macro is invoked:
static_toml::static_toml! {
/// Example TOML file from [toml.io](https://toml.io/en).
#[allow(missing_docs)]
#[derive(Debug)]
#[static_toml(suffix = Toml)]
pub(crate) static EXAMPLE = include_toml!("example.toml");
}Note: In the subsequent sections showing generated values, it is assumed that the macro is invoked with default configurations. This assumption is made to keep the examples concise and focused on illustrating the structure of the generated code, rather than the effects of various configuration options.
Alongside an official example TOML file structured as follows:
# This is a TOML document
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 }
[servers]
[servers.alpha]
ip = "10.0.0.1"
role = "frontend"
[servers.beta]
ip = "10.0.0.2"
role = "backend"The macro begins by parsing the entire input.
During this process, it identifies and extracts documentation comments,
derive attributes, static_toml attributes, other attributes,
visibility modifiers (if any), the name of the static value, and the
file path of the TOML file.
The elements like static keyword, = sign, and the pseudo include_toml!
macro are utilized to create a Rust-like syntax, ensuring that IDEs provide
appropriate syntax highlighting.
The macro then applies the extracted documentation comments to the static
value, and derive attributes are applied to every data type that is
generated. This is particularly useful when deriving traits that require all
fields to implement the same trait.
Other attributes are applied to the root module of the generated data types.
The static_toml attribute is used exclusively for configuring the macro
invocation and doesn’t appear in any generated code.
Visibility modifiers are applied to both the static value and the outermost module that contains the data types.
The file path specified in the include_toml! macro is utilized to locate
the TOML file.
Since integrating external files during macro expansion can be challenging,
the provided path is concatenated with the
CARGO_MANIFEST_DIR
environment variable during the build. This causes the path resolution to
always start from the root of the top-level crate, which currently hampers
the macro’s compatibility with libraries due to path resolution challenges.
If you need support for library usage and have any suggestions or workarounds, please open an issue in the GitHub repository.
As for the output, the macro generates a static value, a module containing
the data types that represent the TOML content, and a constant named _
that leverages the include_str! macro to include the TOML file.
This usage of the include_str! macro ensures that the compiler is aware
of the file dependency, and as such, it will trigger a recompilation if the
file changes.
Furthermore, since this constant is named _, the compiler should optimize
it out, leaving no trace in the final binary.
§Generated Data Types
To statically embed a TOML file and facilitate its use in your Rust application, the macro generates data types capable of representing the structure of the included TOML file. Initially, the macro reads the TOML file as a string and parses it using the toml crate. Subsequently, the macro traverses the resulting data structure to create the necessary Rust types.
During this traversal, the macro generates structs and type aliases.
For primitive values, a type alias is created with its name being the key
used in the TOML file, converted to PascalCase.
The underlying data type for these aliases will be primitive types like
i64 for integers, f64 for floating-point numbers, bool for booleans,
and &'static str for strings.
For structured data, the macro generates structs, where the struct’s name is
derived from the key in the TOML file, converted to PascalCase.
The fields within these structs are named after the keys in the structured
data, converted to snake_case.
For arrays in the TOML file, the macro generates slices or tuples, depending
on the complexity and uniformity of the elements.
The crate-level documentation provides more information on the
conditions under which fixed-size slices or tuples are generated.
To facilitate this, the macro employs a custom type equality function
(attached via a trait to toml::Value) that recursively checks whether
two TOML values necessitate the exact same data type.
If all elements within an array can be represented using the same data type,
slices are used.
Because the macro could generate multiple data types with the same name, it organizes them into separate modules to avoid name collisions.
Below is an example representation of the data types generated for the example TOML file provided earlier:
mod example {
pub struct Example {
pub database: database::Database,
pub owner: owner::Owner,
pub servers: servers::Servers,
pub title: title::Title
}
pub mod database {
pub struct Database {
pub data: data::Data,
pub enabled: enabled::Enabled,
pub ports: ports::Ports,
pub temp_targets: temp_targets::TempTargets
}
pub mod data {
pub struct Data(pub values_0::Values0, pub values_1::Values1);
pub mod values_0 {
pub type Values0 = [values::Values; 2usize];
pub mod values {
pub type Values = &'static str;
}
}
pub mod values_1 {
pub type Values1 = [values::Values; 1usize];
pub mod values {
pub type Values = f64;
}
}
}
pub mod enabled {
pub type Enabled = bool;
}
pub mod ports {
pub type Ports = [values::Values; 3usize];
pub mod values {
pub type Values = i64;
}
}
pub mod temp_targets {
pub struct TempTargets {
pub case: case::Case,
pub cpu: cpu::Cpu
}
pub mod case {
pub type Case = f64;
}
pub mod cpu {
pub type Cpu = f64;
}
}
}
pub mod owner {
pub struct Owner {
pub dob: dob::Dob,
pub name: name::Name
}
pub mod dob {
pub type Dob = &'static str;
}
pub mod name {
pub type Name = &'static str;
}
}
pub mod servers {
pub struct Servers {
pub alpha: alpha::Alpha,
pub beta: beta::Beta
}
pub mod alpha {
pub struct Alpha {
pub ip: ip::Ip,
pub role: role::Role
}
pub mod ip {
pub type Ip = &'static str;
}
pub mod role {
pub type Role = &'static str;
}
}
pub mod beta {
pub struct Beta {
pub ip: ip::Ip,
pub role: role::Role
}
pub mod ip {
pub type Ip = &'static str;
}
pub mod role {
pub type Role = &'static str;
}
}
}
pub mod title {
pub type Title = &'static str;
}
}Note that the order of the generated token stream may differ from the TOML file. This discrepancy arises from how the toml::Value represents the structure via binary trees where the keys are sorted. However, this does not affect the integrity of the data representation, and ordered data such as arrays are preserved correctly.
Furthermore, all internal fields within the generated structs are public, allowing for easy access to the values. However, it’s essential to realize that making fields public is a strong commitment and can impact future compatibility. This is yet another reason why this crate is recommended for use in binaries rather than libraries.
§Generated Static Value
The primary purpose of this macro is to generate a static representation of the TOML file you want to embed. This static representation encompasses all the values contained within the TOML file.
Here’s what the generated static value would look like for the example TOML file discussed earlier:
static EXAMPLE: example::Example = example::Example {
database: example::database::Database {
data: example::database::data::Data(["delta", "phi"], [3.14f64]),
enabled: true,
ports: [8000i64, 8001i64, 8002i64],
temp_targets: example::database::temp_targets::TempTargets {
case: 72f64,
cpu: 79.5f64
}
},
owner: example::owner::Owner {
dob: "1979-05-27T07:32:00-08:00",
name: "Tom Preston-Werner"
},
servers: example::servers::Servers {
alpha: example::servers::alpha::Alpha {
ip: "10.0.0.1",
role: "frontend"
},
beta: example::servers::beta::Beta {
ip: "10.0.0.2",
role: "backend"
}
},
title: "TOML Example"
};The macro constructs a static value based on the generated data types, and initializes it with the contents of the TOML file. This allows your program to access the configuration data at compile time without needing to parse it at runtime.
§Configuration Details
The usage of the configuration options is explained in the crate level documentation. Here, we will outline the various options available for configuration with some implementation details.
-
#[static_toml(prefix = Prefix)]This attribute allows you to specify a prefix that will be added to the data types generated by the macro. The prefix should be a valid identifier. It’s recommended to use
PascalCasefor the prefix, as this aligns with Rust’s naming conventions. If it isn’t inPascalCase, the compiler will issue a warning. You can suppress this warning by applying the#[allow(non_camel_case_types)]attribute, but it is not recommended.
-
#[static_toml(suffix = Suffix)]Similar to the prefix attribute, this adds a suffix to the generated data types. Note that if the suffix isn’t capitalized, the compiler won’t issue a warning, but the resulting names may not adhere to Rust’s naming conventions.
-
#[static_toml(root_mod = toml)]This attribute sets the identifier for the root module that will contain the data types for the TOML file. This identifier also becomes the name of the root data type among the generated types. If this is not set, the name of the static value is converted to
snake_caseand used as the root module name.
-
#[static_toml(values_ident = values)]When generating data types for arrays, a separate namespace is needed. By default, this macro uses
valuesfor naming the modules and data types. This attribute allows you to specify a different name. For example, changing the value to “items” would alter the names of the generated modules and data types accordingly. Note that this configuration does not influence the keyvaluein the TOML file.
-
#[static_toml(prefer_slices = true)]Determines whether the macro should attempt to generate fixed-size slices when handling arrays. If set to
false, tuples will be generated for all arrays. The default value istrue. Disabling this option will not only prevent the generation of fixed-size slices but also skip the equality check between array items, which might marginally speed up the compilation process.
-
#[static_toml(cow)]This option replaces
'staticslices (&'static str) and arrays ([T; N]) withstd::borrow::Cow<'static, str>andstd::borrow::Cow<'static, &[T]>, respectively.The generated values are transformed from:
"some string"tostd::borrow::Cow::Borrowed("some string")[1i64, 2i64, 3i64]tostd::borrow::Cow::Borrowed(&[1i64, 2i64, 3i64])
The
Cowis fully qualified to ensure hygiene and prevent conflicts with other imports that might define aCow.
Below is an example that illustrates how changing the values_ident to
“items” affects the generated structure:
# TOML file
[[list]]
value = 123
[[list]]
value = 456
[[tuple]]
a = 1
[[tuple]]
b = 2// With `values_ident` set to "items"
mod lists {
pub struct Lists {
pub list: list::List,
pub tuple: tuple::Tuple
}
pub mod list {
pub type List = [items::Items; 2];
pub mod items {
pub struct Items {
pub value: value::Value
}
mod value {
pub type Value = i64;
}
}
}
pub mod tuple {
pub struct Tuple(pub items_0::Items0, pub items_1::Items1);
pub mod items_0 {
pub struct Items0 {
pub a: a::A
}
pub mod a {
pub type A = i64;
}
}
pub mod items_1 {
pub struct Items1 {
pub b: b::B
}
pub mod b {
pub type B = i64;
}
}
}
}Note that the value identifiers within the TOML file are unaffected by this configuration setting.
// With `values_ident` not being set
mod lists {
pub struct Lists {
pub list: list::List,
pub tuple: tuple::Tuple
}
pub mod list {
pub type List = [values::Values; 2];
pub mod values {
pub struct Values {
pub value: value::Value
}
mod value {
pub type Value = i64;
}
}
}
pub mod tuple {
pub struct Tuple(pub values_0::Values0, pub values_1::Values1);
pub mod values_0 {
pub struct Values0 {
pub a: a::A
}
pub mod a {
pub type A = i64;
}
}
pub mod values_1 {
pub struct Values1 {
pub b: b::B
}
pub mod b {
pub type B = i64;
}
}
}
}§Error Handling
When using this macro, you might encounter errors in various scenarios. The macro is designed to provide descriptive error messages that will help you to identify and fix issues quickly. Here is an overview of the types of errors you might encounter and how they are handled:
TOML Parsing Errors
If the TOML file included is malformed or contains syntax errors, the macro will return a compile-time error with details about the parsing issue. Make sure that the TOML file is valid and follows the TOML specification.
Configuration Errors
The macro accepts several configuration options through attributes (like
prefix, suffix, etc.).
If there is an issue with these configuration options
(e.g., invalid values), you will get a descriptive error message.
File Not Found Errors
If the TOML file specified to be embedded is not found, a compile-time error will be triggered.
Handling Errors
When you encounter an error, read the error message carefully as it usually contains information on what went wrong. Correct the TOML file or configuration options as suggested by the error message and try again.
In case the error messages are not descriptive enough or you encounter an unexpected error, consider creating an issue in the project repository.