Search code examples
rustmacrosrust-macros

How do I write a macro that returns the implemented method of a struct based on a string?


Inspired by How to get struct field names in Rust?, I want to get an implemented method of the struct based on a str, something like:

macro_rules! comp {
    (struct $name:ident {
        $($field_name:ident : $field_type:ty,)*
    }
    impl $name2:ident {
        $(pub fn $func_name:ident($($args:tt)*) $bk:block)*
    }

    ) => {
        //basic component
        struct $name {
            $($field_name: $field_type),*
        }
        impl $name {
            $(pub fn $func_name($($args)*) $bk)*

            // the generated function
            pub fn get_method(index: &str) -> &'static dyn Fn() {
                $(if stringify!($func_name) == index {
                    return $func_name;                               // (***)
                })*
                //
                // Some code here to return the right function
                //
            }
        }
    };
}

fn main() {
    comp! {
        struct S {
            field: String,
        }
         impl S {
            pub fn method1() {
                println!("method1 called");
            }
            pub fn method2() {
                println!("method2 called");
            }
        }
    }
    // the functionality should achieved
    // S::get_method("method1") == S::method1
    // S::get_method("method2") == S::method2
}

Originally I wanted to get the function pointer with return $func_name, but it seems impossible; the line of code marked with (***) gets the error:

error[E0423]: expected function, found macro `stringify`
  --> src/main.rs:19:22
   |
19 |                   $(if stringify($func_name) == index {
   |                        ^^^^^^^^^ not a function
...
31 | /     comp! {
32 | |         struct S {
33 | |             field: String,
34 | |         }
...  |
42 | |         }
43 | |     }
   | |_____- in this macro invocation
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
help: use `!` to invoke the macro
   |
19 |                 $(if stringify!($func_name) == index {
   |                               ^

error[E0425]: cannot find value `method1` in this scope
  --> src/main.rs:36:20
   |
36 |             pub fn method1() {
   |                    ^^^^^^^ not found in this scope

error[E0425]: cannot find value `method2` in this scope
  --> src/main.rs:39:20
   |
39 |             pub fn method2() {
   |                    ^^^^^^^ not found in this scope

error[E0308]: mismatched types
  --> src/main.rs:19:19
   |
19 |                   $(if stringify($func_name) == index {
   |  ___________________^
20 | |                     return $func_name;                               // (***)
21 | |                 })*
   | |_________________^ expected reference, found `()`
...
31 | /     comp! {
32 | |         struct S {
33 | |             field: String,
34 | |         }
...  |
42 | |         }
43 | |     }
   | |_____- in this macro invocation
   |
   = note: expected reference `&'static (dyn std::ops::Fn() + 'static)`
              found unit type `()`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

How to get it done?


Solution

  • first of all, I'd like to thank @Shepmaster, your suggestion applies to most cases if your problem is not complicated. However it's required to tackle a struct, a block of code provided by user, in this case, the only way I figure out is use macro rule to achieve that

    after some time, I finally get it done. in our case, for short, we can do

    fn get_method(ind: &str) -> Option<&dyn Fn()> {
        // the default type of $name::$func is fn(), function pointer
        // it is recommended to convert into &dyn Fn() if you impl many methods
        let methods = vec![ $(&$name::$func as &dyn Fn()),* ];
        let inds = vec![$(stringify!($func)),*];
        let mut i = 0;
        for item in inds.iter() {
            if item == &ind {
                break;
            } else {
                i +=1;
            }
         }
         if i <= inds.len() - 1 {
             Some(methods[i])
         } else {
             None
         }
         
    }