Let's say there is a config struct that decides some behavior in some library:
struct Config {
pub use_foo: bool,
pub far_enabled: bool,
pub title: String,
}
Right now, adding any new configuration options is a breaking change. This is undesirable because I may want to expand the library's functionality in the future.
At first, I considered using #[non_exhaustive]
, because that's supposed to signal to the user (and the compiler) that more fields may be added in the future. If my Config
implemented Default
, I assumed that meant that downstream users could just use this syntax:
let config = Config {
use_foo: false,
..Default::default()
}
That way, any new fields that are added will be given their default values. Seems perfectly reasonable, right? Well, that's forbidden by the compiler:
error[E0639]: cannot create non-exhaustive struct using struct expression
Looking up that error code, it looks like #[non_exhaustive]
is only meant to signal that more fields may be added in the future - the exact behavior to enforce that does not seem exactly defined.
Why am I not allowed to construct using an existing instance which already has all the fields? That would allow users to specify the config options they want to change while new fields will simply be set to their defaults.
I think #[non_exhaustive]
should allow you to use struct expressions if you also use ..<some_instance_of_the_struct>
. That way, the compiler can guarantee that you will have all the fields, because any fields you miss in your expression can be filled in by the fields of that <some_instance_of_the_struct>
. Default::default()
fills this role perfectly and would be useful for configuration.
Thoughts?
Please note that the next best alternative is the BuilderBuilderBuilder pattern and I don't want to force that on my users for any reason.
let config_builder_builder: ConfigBuilderBuilder<BuilderVersionOne> = ConfigBuilderBuilderBuilder::new()
.use_builder_builder_pattern_version(BuilderBuilderPatternVersion::One)
.builder_builder();
let config_builder = config_builder_builder
.add_support_for_option(ConfigOption::InvertY) // ConfigOption is non_exhaustive
.builder();
let config = config_builder
.set_config_option(ConfigOption::InvertY) // not specifying the option panics
// specifying unsupported options panics
.build_config();
// now you have a config