Abstract
I am creating a Rust application with a large amount of input parameters.
In the rust world, should I prefer a configuration file (with well-selected format) or a library API (easy with rust ecosystem) ?
Can you provide well established rust application examples ? (with one or the other way)
Intro
I have a very hard time choosing between configuration file and library API.
I tried many hits on research engines but didn’t find a post/question focused on my problem.
Here is a full overview of my journey with this problem.
Example
I am working on an application which needs a definition of materials (chemical elements), geometry (surfaces/volumes) and general parameters (numbers of threads, solver selection, ...). Let’s say an application which needs a large amount of input parameters (not all defined at this development phase -> ease of extensibility needed)
Passing all these arguments to the command line is not suitable due to the amount of parameters needed.
In fact, I did some tests with clap initially but when my number of input parameter and all possibilities grew exponentially my command line became a full dissertation ...
Two solution (that I found for now...) are available :
- Configuration file
- Library API
Configuration file
What I define as a configuration file is a simple file (human readable and human writable) containing all the parameters needed by my application. The format can be one of all commonly used formats:
- JSON
- TOML
- YAML
- XML
- « key = value » format
- ASCII (with custom syntax and custom parser)
Library API
My application can also provide a full API providing all convenient structures/methods/functions for the user to initialize my application parameters and launch the calculation/simulation/computational step.
Problem
Initially, using configuration file seems to be the most simple/convenient solution at first sight but my research on these formats leads to the following point:
As soon as my parameters/problems do not fit the selected format (key-value does not allow hierarchical format, JSON does not allow non-string map key, ...) defining all the parameters becomes a real challenge.
That’s why defining a custom (ASCII) format appears. This includes the full definition of grammar/syntax and a parser ( maintainability). Adding parameters and new features can lead to a significant amount of changes (syntax + parser).
Introducing a new grammar and syntax led me to the direct use of a programming language: the rust language via a library API. This solution includes the need to create a library alongside my binary (relatively easy with cargo) but also the necessity to document all structures/enum/functions associated with this API. The main difficulty with this API is to make public the minimal amount of method needed and expose me to the risk of breaking change for each update.
Journey
This is what I tried so far and the difficulties I faced :
Simple key value format
I started this journey with a simple key value configuration file:
// snip
name = "example"
threads = 4
// snip
Pros:
- really simple to parse
- extensibility
Cons:
- do not accept hierarchical structures (reason why I first used next step)
JSON TOML YAML XML ...
I tried some common formats for my parameters. They all support hierarchy and are human readable/writable (XML hit the limit for me for the human writable aspect).
They are widely supported and using them was pretty easy with all the libraries available in Rust.
At the beginning, I use them by writing my input file by hand. However, I realized I needed some recursion/references for some parameters afterwards. At this moment, I decided to use rust code and generate my structures and then serialize/deserialize them with serde.
At first this solution was fine but I hit the limits of these formats :
- JSON: non string key for maps is not compliant JSON
- XML: readability
- TOML: hierarchy with tables introduced many sections and readability diminish with the amount of parameters introduced
- YAML : indentation/non string key for maps format
The final limit for all these formats was the fact I actually needed some advanced syntax not available in these formats.
« I start from scratch » syndrome
I always get this phase in my development process. The actual thought is: « I didn’t find what I needed let’s try it by myself». I started to write my custom ASCII format and the parser for the syntax I just introduced. At first sight I was proud of my work but when I tried it, my format felt ... incoherent and error prone.
If my input was malformed, controlling the flow of the parser and providing well formatted and established error message is a tedious task. Eventually I felt overwhelmed by this solution (and I spent much more time on it that I would like to admit)
Library API
Eventually, I turn my internal code structures/enums/functions into a library (easy with cargo).
The main idea for the user is to download my library, add a dependency and create his own main function.
Download my library into "lib"
$ git clone ...
Create a binary application
$ cargo new app
$ cd app
Append a dependency on lib
# Cargo.toml
[dependencies]
lib = { path = "../lib" }
Create the main script
// main.rs
use lib::*;
fn main() {
let config = lib::Config::new(...);
let solver = lib::Solver::Variant;
lib::run(solver, config)
}
All of these steps are easy in rust and very limited in other language (C, C++). Adding a dependency, compiling and linking can be a hard task in C/C++.
This approach includes following problems:
- My internal structures were private and making them public expose me to breaking changes with my application
- A full documentation is needed
- The user must know the rust ecosystem
Question
In the rust world, should I prefer a configuration file (with well-selected format) or a library API (easy with rust ecosystem) ?
It would be really nice if you can provide some well established rust application examples.
Notes
- I may have missed another solution, do not hesitate to lead me to other way I didn't explore (configuration file and library API can be a limited subset of available solutions).
Thanks