I have a file main.rs and a file rule.rs. I want to define functions in rule.rs to be included in the Rules::rule vector without having to push them one by one. I'd prefer a loop to push them.

main.rs:

struct Rules {
    rule: Vec<fn(arg: &Arg) -> bool>,
}

impl Rules {
    fn validate_incomplete(self, arg: &Arg) -> bool {
        // iterate through all constraints and evaluate, if false return and stop
        for constraint in self.incomplete_rule_constraints.iter() {
            if !constraint(&arg) {
                return false;
            }
        }
        true
    }
}

rule.rs:

pub fn test_constraint1(arg: &Arg) -> bool {
    arg.last_element().total() < 29500
}

pub fn test_constraint2(arg: &Arg) -> bool {
    arg.last_element().total() < 35000
}

Rules::rule should be populated with test_constraint1 and test_constraint2.

In Python, I could add a decorator @rule_decorator above the constraints which you want to be included in the Vec, but I don't see an equivalent in Rust.

In Python, I could use dir(module) to see all available methods/attributes.

Python variant:

class Rules:

    def __init__(self, name: str):
        self.name = name
        self.rule = []

        for member in dir(self):
            method = getattr(self, member)
            if "rule_decorator" in dir(method):
                self.rule.append(method)

    def validate_incomplete(self, arg: Arg):
        for constraint in self.incomplete_rule_constraints:
            if not constraint(arg):
                return False
        return True

With the rule.py file:

@rule_decorator
def test_constraint1(arg: Arg):
    return arg.last_element().total() < 29500

@rule_decorator
def test_constraint1(arg: Arg):
    return arg.last_element().total() < 35000

All functions with a rule_decorator are added to the self.rule list and checked off by the validate_incomplete function.

1 Answers

0
Lukas Kalbertodt On

Rust does not have the same reflection features as Python. In particular, you cannot iterate through all functions of a module at runtime. At least you can't do that with builtin tools. It is possible to write so called procedural macros which let you add custom attributes to your functions, e.g. #[rule_decorator] fn foo() { ... }. With proc macros, you can do almost anything.

However, using proc macros for this is way too over-engineered (in my opinion). In your case, I would simply list all functions to be included in your vector:

fn test_constraint1(arg: u32) -> bool {
    arg < 29_500
} 
fn test_constraint2(arg: u32) -> bool {
    arg < 35_000
}

fn main() {
    let rules = vec![test_constraint1 as fn(_) -> _, test_constraint2];

    // Or, if you already have a vector and need to add to it:
    let mut rules = Vec::new();
    rules.extend_from_slice(
        &[test_constraint1 as fn(_) -> _, test_constraint2]
    );
}

A few notes about this code:

  • I replaced &Arg with u32, because it doesn't have anything to do with the problem. Please omit unnecessary details from questions on StackOverflow.
  • I used _ in the number literals to increase readability.
  • The strange as fn(_) -> _ cast is sadly necessary. You can read more about it in this question.