How to simplify warning for each field that is None?

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.");
      }
  
      [...]
  }


}


A quick and dirty macro perhaps?

2 Likes

Almost the same macro (adds destructuring).

3 Likes

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.:

impl Structer {
    // example access
    fn burgers(&self) -> Option<&str> {
        self.foods[StructerFood::Burgers].as_deref()
    }

    // this one actually got simpler though…
    fn new() -> Self {
        Self {
            foods: enum_map! {
                _ => None
            },
        }
    }

    fn demo_exhaustive() -> Self {
        use StructerFood::*;
        Self {
            foods: enum_map! {
                Donuts => Some("value1".into()),
                Burgers => Some("I love burgers!?".into()),
                Fishes => None,
                Broccolis => Some("42".into()),
            },
        }
    }
}
3 Likes

A struct full of optional fields is usually a code smell.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.