3

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ward
  • 131
  • 2
  • 8
  • Hi there! Unfortunately, I am really confused by your question. So you want to add function pointers to a `Vec`, ok. You don't want to push them one by one (why?), but via a loop, ok. Why don't you use a loop then? What has all of this to do with decorators? Also: please provide a [MCVE] (particularly, we have no idea what `Arg` is. Also: `validate_incomplete`, the `Rules` wrapper and the different modules seem completely irrelevant to the main question). Could you try to explain your problem differently/with more detail? – Lukas Kalbertodt Apr 13 '19 at 09:17
  • If i would use a loop, how would the loop know which functions to select from the rule.rs file if I can't give them a decorator? I do not wan't to push them line by line with .push, because there would be more than 20... – Ward Apr 13 '19 at 09:20
  • 1
    This is precisely what the `#[test]` annotations for the test-suite do, but they are special cased in the compiler, and while a generalization was proposed for custom test frameworks, it is still constrained to test suites. – Jan Hudec Apr 13 '19 at 11:40
  • See also [How to introspect all available methods & members in Rust?](https://stackoverflow.com/q/39266001/155423); [How can I statically register structures at compile time?](https://stackoverflow.com/q/32678845/155423); [How can I provide a custom test attribute?](https://stackoverflow.com/q/50786312/155423); – Shepmaster Apr 13 '19 at 13:40

2 Answers2

1

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.
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • 1
    I don't think that procedural macros would even work here, as there's no way for them to "build up" a vector across the separate invocations. You'd have to wrap the *entire file* in the macro for it to work, I believe. See also [Jan Hudec's comment](https://stackoverflow.com/questions/55663699/how-to-include-a-list-of-functions-from-another-module-with-decorator#comment98016861_55663699), which mentions the current hack. – Shepmaster Apr 13 '19 at 13:38
  • WDYTA duplicate of [How to introspect all available methods & members in Rust?](https://stackoverflow.com/q/39266001/155423) – Shepmaster Apr 13 '19 at 13:38
  • @Shepmaster Good point about proc macros. I guess it would be possible with dirty hacks, but yeah, I agree. About duplicate: it doesn't fit perfectly (as this question has a specific goal: creating a vector of function pointers), but I wouldn't mind closing this as dupe. – Lukas Kalbertodt Apr 13 '19 at 13:48
1

You can, with some tweaks and restrictions, achieve your goals. You'll need to use the inventory crate. This is limited to Linux, macOS and Windows at the moment.

You can then use inventory::submit to add values to a global registry, inventory::collect to build the registry, and inventory::iter to iterate over the registry.

Due to language restrictions, you cannot create a registry for values of a type that you do not own, which includes the raw function pointer. We will need to create a newtype called Predicate to use the crate:

use inventory; // 0.1.3

struct Predicate(fn(&u32) -> bool);
inventory::collect!(Predicate);

struct Rules;

impl Rules {
    fn validate_incomplete(&self, arg: &u32) -> bool {
        inventory::iter::<Predicate>
            .into_iter()
            .all(|Predicate(constraint)| constraint(arg))
    }
}

mod rules {
    use super::Predicate;

    pub fn test_constraint1(arg: &u32) -> bool {
        *arg < 29500
    }
    inventory::submit!(Predicate(test_constraint1));

    pub fn test_constraint2(arg: &u32) -> bool {
        *arg < 35000
    }
    inventory::submit!(Predicate(test_constraint2));
}

fn main() {
    if Rules.validate_incomplete(&42) {
        println!("valid");
    } else {
        println!("invalid");
    }
}

There are a few more steps you'd need to take to reach your originally-stated goal:

  • "a vector"

    You can collect from the provided iterator to build a Vec.

  • "decorated functions"

    You can write your own procedural macro that will call inventory::submit!(Predicate(my_function_name)); for you.

  • "from a specific module"

    You can add the module name into the Predicate struct and filter on that later.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366