Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differences between config-rs and figment? #371

Open
wizard-28 opened this issue Aug 24, 2022 · 5 comments
Open

Differences between config-rs and figment? #371

wizard-28 opened this issue Aug 24, 2022 · 5 comments

Comments

@wizard-28
Copy link

The figment crate looks pretty similar to this crate. What are the differences between the two?

@paulgb
Copy link

paulgb commented Sep 3, 2022

I've recently done an evaluation of the two for use in a project. You're right that they accomplish a similar goal, and they both have sufficient functionality for my use-case.

In terms of proxies for adoption, config-rs clearly has an edge:

config-rs figment
Star count 1.6k 211
Last commit 3 days ago > 1 year ago
Reverse dependencies 328 35

I didn't evaluate the code of both deeply, but superficially they are similar in scale.

config-rs figment
Source lines of code 3,481 3,664
Test lines of code 3,218 147

I found the documentation for figment far more helpful than that of config, which points you to examples. To get the functionality I wanted from config I ended up reading a bunch of code, which I didn't have to do for figment.

My use case was to load some data from a .toml file, and allow for environment overrides. Here's what that looks like in figment:

    let config: MyappConfig = Figment::new()
        .merge(Toml::file("config.toml"))
        .merge(Env::prefixed("MYAPP_").split("__"))
        .extract()
        .unwrap();

Here's the config-rs version:

    let config: MyappConfig = config::Config::builder()
        .add_source(config::File::new("config.toml", config::FileFormat::Toml))
        .add_source(
            config::Environment::with_prefix("MYAPP")
                .separator("__")
                .prefix_separator("_")
                .try_parsing(true)
                .list_separator(",")
                .with_list_parse_key("foo.bar")
        )
        .build()
        .unwrap()
        .try_deserialize()
        .unwrap();

The difference in complexity mostly stems from the way both handle defining lists in environment variables, which was a requirement for my use-case. In figment, environment variables are parsed according to these rules, so to override a list you pass a value that looks like [foo, bar] and as long as the receiving type expects a Vec<String> or similar, it just works.

With config-rs, the behavior is more ad-hoc, and I had to make a flow chart to understand it:

image

The difference is that for numeric types and bools, config-rs will parse them (if parsing is enabled), but for arrays you specify the keys that it should parse as arrays. Although it's not mentioned in the docs as far as I can tell, the "key" refers to a dot-delimited path of lowercased field names. As a result, type definitions end up duplicated between your serde type definitions and the environment parser configuration, which I don't love.

Overall, there's not a clear winner IMHO, although given the inactivity around figment it may need to be forked to be viable. Both seem like very usable and good libraries to me.

@matthiasbeyer
Copy link
Member

Wow, thanks for that rather in-depth review! I am in the process of re-thinking config-rs (although I am very slow) and I definitively want to make that environment parsing less error-prone and more flexible for downstream users! So thanks for pointing that out again!

@SergioBenitez
Copy link

SergioBenitez commented Apr 1, 2023

Hi all! Thanks for taking a look at Figment. I just wanted to clarify the few negatives you've pointed out.

First, Figment is maintained. There are few commits, not because of maintenance issues, but because there have been almost no issues discovered in the library since its introduction. There are no known bugs, nor are there any feature requests outstanding. It's just been a really, really stable library that does, and has done, what it says it does.

Second, I believe the "lines of test code" metric is incorrect. Much of the testing in figment is done via doc tests, and I imagine this isn't being counted. The library is really well tested, as evidenced by the sparse bug reports.

Finally, the thing that Figment does that no other configuration library in Rust does well is perfectly track the provenance of configuration values. Among other things, this allows you to emit error messages that point to the actual source of configuration that caused the error. This makes up the vast majority of the code, and is incredibly tricky to implement in a manner that still gives flexibility to the user.

Figment also has productivity features that are unique to it. My favorite one is magic, especially RelativePathBuf, which is a PathBuf that knows the path of the file it was configured in. This is made possible by the precise provenance tracking unique to figment.

I have done no advertising of Figment, but I believe that more users should be aware of it. Taking a read through the config-ng vision documents, it's clear to me that Figment already does almost everything being wished for in the document, and everything it doesn't already do can very easily and idiomatically be done by external support crates, facilitated by Figment's core. I would love to find some way to not duplicate efforts. One idea is deprecating config-rs in favor of Figment and releasing external support libraries for Figment for that extra functionality. Other ideas would be aliasing config to figment so that config is figment and pointing users to figment otherwise. In any case, I think this is an area in which it would be useful to deduplicate efforts.

@paulgb
Copy link

paulgb commented Apr 1, 2023

Thanks Sergio, I’ll give Figment another look for my use case.

I think I used scc for the source code counts, so you're probably right that it ignores doctests, sorry about that. I also realize that LOC (for both tests and src) is a pretty bad metric to begin with.

@jreniel
Copy link

jreniel commented Dec 26, 2023

Hello! I just want to give my two cents. I recently started testing config to try and loosely mimic the behavior of pydantic, but in Rust. However, I was quite dissapointed when I realized that the errors in config won't contain any information about the provenance of the error. For example, in my case I have:

thread 'main' panicked at XXXX.rs:21:65:
called `Result::unwrap()` on an `Err` value: TryFromError(invalid type: integer `1500`, expected a tuple of size 2)

I couldn't find a way to get where the error was coming from, so that's when I started exploring these issues and landed here. If figment can give the information of the provenance, then at least to me, that is a must. So I will be testing figment for that as well. To be honest, Rust being so "new" it's normal to see lots of effort duplications, but what's important is that we learn from each other and work together for our common goals. For that, I want to thank you all for the great effort you are putting in config, which so far looks almost exactly like what I need, but I definitely do need the error provenance, and that's why I will be giving Figment a try.

@epage epage pinned this issue Jan 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants