Require trait in associated type

I have such a simple trait with an associated type and a struct:

pub trait Validatable{
  type ValidatorCallback: Fn(&Self)->bool;
}

pub struct ValidationRule<F>
where
  F: Validatable,
{
  pub validator: F::ValidatorCallback,
  pub error_message: fn(&F)->String,
  pub fallback: ValidationFallback
}

The problem is that if I want to implement this trait to any other struct, it requires me to implement ValidatorCallback on that struct. And I can't simply do it like this:

impl Validatable for RawProject{
  type ValidatorCallback = Fn(&Self)->bool;
}

because of the error

error[E0782]: trait objects must include the `dyn` keyword

and also adding dyn is not helping a lot. How do I solve it?

You need to name a concrete type; traits are not types. Unfortunately, function items and closures don't have nameable types, so your choices are type erasure (dyn Fn) or function pointer indirection:

type ValidatorCallback = fn(&Self) -> bool
type ValidatorCallback = Box<dyn Fn(&Self) -> bool>
type ValidatorCallback = Box<dyn Send + Sync + Fn(&Self) -> bool>
type ValidatorCallback = Arc<dyn Fn(&Self) -> bool>
// ...

The limited gain in flexibility over just hard-coding which type-erased form you want in ValidatoinRule may not be worth it.

I was trying to avoid using Boxes and also I can't use fn since this closures should capture variables

what do you mean by hardcoding?

There's also &'a dyn Fn(&Self) -> bool, but chances are you[1] will prefer a Box to dealing with lifetimes.

 pub struct ValidationRule<F>
-where
-  F: Validatable,
 {
-  pub validator: F::ValidatorCallback,
+  // type-erased container of your choice
+  pub validator: Box<dyn Fn(&F)->bool>,
   pub error_message: fn(&F)->String,
   pub fallback: ValidationFallback
 }

  1. or your consumers ↩︎

Thank you for your help. Eventually I ended with such a solution:

pub struct ValidationRule<'a, F>
where
  F: Validatable,
{
  pub validator: Box<dyn Fn(&F) -> bool + 'a>,
  pub error_message: fn(&F) -> String,
  pub fallback: ValidationFallback,
}

pub struct Validator<'a, F>
where
  F: Validatable,
{
  pub rules: Vec<ValidationRule<'a, F>>,
}

It seems like I was not able to avoid using lifetimes here because of my closures which should capture variables.
also here is how looks the actual usage:

let validator = Validator::<RawProject>::new(vec![
    ValidationRule {
      error_message: |project| format!("project '{}' does not have language tags", project.title),
      fallback: ValidationFallback::Error,
      validator: Box::new(|project| project.has_any_tags(tag_categoies.get("language").unwrap())),
    },
    ValidationRule {
      error_message: |project| {
        format!(
          "project '{}' does not have webdev/gamedev tags",
          project.title
        )
      },
      fallback: ValidationFallback::Warning,
      validator: Box::new(|project| {
        project.has_any_tags(&HashSet::from(["webdev".into(), "gamedev".into()]))
      }),
    },
  ]);

Not sure if I understand right, but given the code of the OP, it is possible to use generics and avoid type erasure if the struct for which Validatable is to be implemented has a type parameter for the closure:

pub struct ValidationFallback;

pub trait Validatable{
    type ValidatorCallback: Fn(&Self) -> bool;
}

pub struct ValidationRule<F>
where
    F: Validatable,
{
    pub validator: F::ValidatorCallback,
    pub error_message: fn(&F) -> String,
    pub fallback: ValidationFallback
}

pub struct RawProject<F> {
    pub validator_callback: F,
}

impl<F: Fn(&Self) -> bool> Validatable for RawProject<F> {
  type ValidatorCallback = F;
}

(Playground)

But type erasure might be more handy. I don't know.

while it does work, I don't think it is good idea to mix validator stuff inside validated structures itself

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.