How to design an enum where one variant can store a closure

Hi there,

I'm trying to define a function that is able to apply a filter to a list of objects. The filter could be either a simple String, a more complex structure or a closure. I tent to design this as an enum lie this:

pub enum ServiceFilter<F: Fn(&Value) -> bool> {
  Name(String),
  Object(Map<String, Value>),
  Function(F),
}

However, when using this approach the generic to store the closure need to be added to the whole enum which makes the actual filter function requiring the generic as well like so:

pub fn filter_services<F>(filter: ServiceFilter<F>) -> Result<Vec<Value>>
where F: Fn(&Value) -> bool
{}

Which is kind of ok, but when using an filter enum variant w/o the closure I would still need to provide some arbitrary type to be passed for the generic even though the actual enum variant does not make any use of it:

let services = filter_services::<fn(&Value) -> bool>(ServiceFilter::Name("myName".to_string()));

Is there an alternative approach using the enum and without boxing the closure? I'd like to prevent creating 3 functions to cover the different filter possibilities if possible.

Thanks in advance for any hint.

You can define a service trait that you implement for String, Map<String, Value> and for closures.

2 Likes

Thanks for the hint - works like a charm :smiley:

Just for others curious as well this is for reference how I solved it:

pub trait Filter {
  fn apply(&self, service: &Value) -> bool;
}

impl Filter for String {
  fn apply(&self, service: &Value) -> bool {
    todo!()
  }
}

impl Filter for Map<String, Value> {
  fn apply(&self, service: &Value) -> bool {
    todo!()
  }
}

impl<F: Fn(&Value) -> bool> Filter for F {
  fn apply(&self, service: &Value) -> bool {
    self(service)
  }
}

pub fn filter_services<F: Filter>(filter: F) -> Result<Map<String, Value>> {
  let services = read_services()?;
  Ok(
    services
      .iter()
      .filter(|(_, service)| filter.apply(service))
      .collect::<Map<_, _>>(),
  )
}

And thus the usage is straight forward:

let services = filter_services("myName".to_string());
let services = filter_services(|srv: &Value| todo!());
2 Likes