config_schemaThe most common use-case should require the least amount of typing. This requires our configuration to have reasonable default values.
Suppose that you have a subsystem that groups a set of functionality together, and you want to nest it under the top-level PlatformConfig.
struct PlatformConfig {
...
#[serde(default)]
my_subsystem: MySubsystem,
}
Provide a Default implementation for MySubsystem if possible and use #[serde(default)] in the PlatformConfig to populate the struct if the entire subsystem is omitted.
Prefer #[derive(Default)] because it is simpler, and use the container-level attribute #[serde(default)] to allow the user to supply only the fields they care about, and let serde fill in the rest.
#[derive(Default)]
#[serde(default)]
struct MySubsystem {
enable_feature1: bool,
enable_feature2: bool,
}
If a specific field needs a specialized default -- such as enable_feature1 needing a default value of true -- avoid using #[derive(Default)] with #[serde(default = "default_true")]. This is a common source of bugs. See below for an example:
#[derive(Default)]
struct MySubsystem {
#[serde(default = "default_true")]
enable_feature1: bool,
enable_feature2: bool,
}
fn default_true -> bool {
true
}
If the user provides a fully empty platform config {}, enable_feature1 will be populated using the #[derive(Default)] which defaults to false.
If the user instead provides { "my_subsystem": {} }, enable_feature1 will be populated using default_true() which returns true.
Use impl Default to keep the deserialization consistent no matter what the user provides.
impl Default for MySubsystem {
fn default() -> Self {
Self {
enable_feature1: true,
enable_feature2: false,
}
}
}
Option<bool> sparinglyTri-state fields are often mis-interpreted and should only be used when their None value has an obvious meaning.
#[derive(Default)]
struct MySubsystem {
enable_feature: Option<bool>,
}
In the above example, it is not clear what the difference is between:
enable_feature = Noneenable_feature = Some(false)enable_feature could be changed to a bool without losing functionality.
One use-case that requires Option<bool> is if the default value of the field should change depending on other configuration values, such as the build type:
let enable_feature = match (build_type, my_subsystem.enable_feature) {
// If nothing is provided, the default value is determined
// based on the build type.
(Eng, None) => true,
(UserDebug | User, None) => false,
// Otherwise, we use the value provided by the user.
(_, Some(enabled)) => enabled,
};