Designing an abstraction over traits in a rules engine

For my turn-based game rules engine, I have a central trait that I use for game callbacks:

pub trait Rule<T> {
  fn on_turn_start(&self, context: &RuleContext<T>) {}
}

This trait has different sub-implementations for different entity types:

pub trait CreatureRule: Rule<Creatue> {}
pub trait PlayerRule: Rule<Player> {}

which ensures that each Rule gets the correct type passed to it as context and allows for callbacks that are specific to one entity type. This is cool, and I can call the trait methods on the boxed rules pretty easily by doing something like:

for rule in player.rules { rule.on_turn_start(RuleContext(player)) }

for creature in creatures {
    for rule in creature.rules { rule.on_turn_start(RuleContext(creature)) }
}

But now I have gotten to the point of having more different types of callbacks and different types of entities, so what I want to do is simplify things by making a helper function that invokes a callback on a configurable set of rules. I thought I could maybe do something like

pub fn execute_rules<T>(
  rules: impl Iterator<Item = Box<dyn Rule<T>>>,
  function: impl Fn(Box<dyn Rule<T>>, RuleContext<T>)) {
}

But this would only work for one specific type T, the type system (correctly) won't let us operate on a heterogeneous set of Rules because it can't know we are passing the correct type each time we call the function.

So what I'm wondering is whether there's a different design approach I could use here to let me safely operate over different sets of Rules without having to write out a bunch of boilerplate code each time I do it.

1 Like

You've stated the problem is that you want to dynamically dispatch on T. There're only a few ways to safely do that:

  1. Get rid of the T parameter and make RuleContext internally dynamic.
  2. Wrap the whole rule structure in a dynamic object that captures and hides the specific details concerning T, such as a closure.
  3. Use Any and attempt to down-cast into each case you want to handle.

All three of these will probably require significant restructuring of your design. Up front, I wouldn't feel comfortable making a more specific recommendation without knowing where the RuleContexts come from, what they contain, and how they need to be managed.

Option 2 is what I would probably start with, but I can't see from the code you've provided whether you're in a good position to capture and use this method. It will depend on who's building the rule sets, who's calling them, where the data can be captured, etc.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.