The below is repetitive, but I need it for my use case. I feel like there should be a way to express this more simply so I don't have to be so repetitive. How can I simplify it?
use tracing::warn;
struct Structer {
donuts: Option<String>,
burgers: Option<String>,
fishes: Option<String>,
broccolis: Option<String>,
[...]
}
impl Structer {
fn warn_if_missing(&self) {
let Self {
donuts,
burgers,
fishes,
broccolis,
[...]
} = self;
if donuts.is_none() {
warn!("`donuts` is missing.");
}
if burgers.is_none() {
warn!("`burgers` is missing.");
}
if fishes.is_none() {
warn!("`fishes` is missing.");
}
[...]
}
}
You could consider changing the representation to some datatype that reflects the fact they are all the same type (Option<String>) and supports iterating. E.g. with a crate like enum_map, this should work:
use enum_map::EnumMap;
use tracing::warn;
#[derive(enum_map::Enum, strum::Display)]
#[strum(serialize_all = "snake_case")]
enum StructerFood {
Donuts,
Burgers,
Fishes,
Broccolis,
}
struct Structer {
foods: EnumMap<StructerFood, Option<String>>,
//[...]
}
impl Structer {
fn warn_if_missing(&self) {
for (name, value) in &self.foods {
if value.is_none() {
warn!("`{name}` is missing.");
}
}
//[...]
}
}
pros: supports uniform handling of those “fields” for other functionality, too. No performance disadvantages, as it’s represented directly in the struct as an array.
cons: maybe your code was just a toy example, and your actual use case is not so uniform, after all; also, this does make the syntax for accessing a specific one a bit more complex, e.g.: