Add struct methods to HashMap?

Hi
I'm not looking to something like reflection, But something that could take advantij of rust's macro system to do the following

  1. Get a list of implemented methods of a struct.
  2. Take a HashMap that gets inputted from the user in the macro.
  3. Add The function name as a string, To the HashMap as key, And value as the pointer, Something that would internally translate to something like hashmap.insert("my_func", MyStruct::my_func);.
    Can this be achieved? OR is it impossible.

Sounds like "reflection" to me.

I have no idea if this is possible somehow or not but I'm curious. If your code can get a list of methods it can presumably use them (call them). But how will that code know what those methods do? How will it know if they are useful to call or not? If a struct has methods on it the programmer already knows, when he writes the code, what they are, why not let the programmer write the code that calls them?

Anyway, it's not clear to me what your user input actually is or what you are actually trying to do with it?

So I'm trying to have a struct that contains network packets then I could call them later from the HashMap by just entering the name, So it would look like

fn parse_packet(&mut self, packet: HashMap<String, String>) {
    if packets.contains_key("function") && self.packets.contains_key(packets["function"]) {
        let p = &self.packets[packets["function"]];
        p(self, packets);
    }
}

So I'm just basically asking for a shortcut where instead of me manually adding every new function I create in the Packets struct to the HashMap, The compiler does it internally, So I could just have a function called setup_packets that calls the macro and everything gets added, It's kind of reflection, But again it's not because I'm expecting it to be done in compile time, But not run time.

https://crates.io/crates/inventory

I'm honestly very confused how does this solve the problem, This crate seems to do an entirely different thing, Some kind of special globals? Not sure, But I don't see how it would be of help, Could you give more context?

Do all the functions involved here have the same signature? That's a prerequisite for this to work, however in general this is not the case so I don't think you'll find a library that implements this. And how/where is the macro supposed to get invoked? This is the biggest limitation, since macro only get the tokens you pass them as input, and they can't know anything outside that, including e.g. the methods of types given only its name.

2 Likes

Yes, They do have the same signature, &mut self and a HashMap, Returns anyhow::Result<()>
So macros can't access implemented methods? IF so that's unfortunate, I was hoping such functionality existed but I guess nothing hurts to manually go call insert for each method if it was the case.
However I am still hoping some way exist. Whether macro or anything else.

Macros can access any code that regular code can, including methods.

But macros cannot enumerate methods without being passed in the tokens for the method definitions, i.e. there would be a need for an attribute macro to be placed on the method definition, or on its surrounding impl block. That attr macro would then work together with other code, possibly including macros, yo achieve what you want.

That's feasible when you control the definitions (though arguably unergonomic) but in general you don't control those and thus this approach is infeasible in general.

The other approach I can think of is to not use macros for this, but some external processor. That would have the advantage that you could e.g. just dump in a (bunch of) file(s) and mine any types and their methods, but maintenance would likely be burdensome because as soon as anything changes in the syntax of Rust (which does happen from time to time, e.g. recently with the addition of GATs), your processor would be out of date, possible fatally so until the discrepancy is fixed.

I don't mind needing to do an attribute macro on each function I want that could be accessed, I think that would actually be nicer and less error prone, Could you give me an example of how such a thing could be done? I'm new to macros so I absolutely have no idea how.

Here ya go :slight_smile:

Overview:
The project consists of a workspace with 2 crates in it:

  • miner-macro, a proc-macro lib which contains the attr macro doing the mining
  • method-miner, a crate that mainly exists to demonstrate how the attribute would be used, as well as a test demonstrating that it works.

Explanation:
The macro parses the item tokens into a value of syn::Item.
Then it deconstructs that value to pick it apart and obtain the method signatures.
For simplicity the macro collects them as strings, and then outputs them to a module named foo_methods. This module appears as a sibling item to the impl block to which the #[mine_methods] attribute is applied i.e. in the same parent module.

The unit test then refers to the generated foo_methods::METHODS value, prints the entries, and calls todo!() to force the output being printed by purposely failing the test.

1 Like

Hey, This is cool
One point though which is the most important, How would I access the function pointer? Lets say I'm expecting a method with 2 parameters, &mut self and a HashMap<String, String>, Return type is anyhow::Result<()>, How could I get the value of fn(&mut StructName, HashMap<String, String>) -> anyhow::Result<()>
I think syn has this somewhere, Though I am unsure what field of ImplItemMethod it is in.

That's easy with the proc-macro @jjpe gives. Like for

#[mine_methods]
impl Foo {
    fn foo(_: &mut StructName, _: HashMap<String, String>) -> anyhow::Result<()> {}
}

You'll need to combine the implementor Foo token with a method name foo token, thus get the function pointer Foo::foo which can be stored as a static too.

I honestly don't understand, As I said I'm very new to macros so I don't think I get the idea.

I think you want this, a map that owns the function names and pointers (all with identical signature).

The code is still very rough with edge cases to be handled. Use macros if you really have many boilerplate codes. Otherwise, it'll be better to write them by hand, since the cost of learning and applying macros can be burdensome.

1 Like
   Compiling method-miner v0.1.0 (C:\Users\mohamed\projects\methodminer\method-miner)
fn(HashMap < String, String >)
Foo :: foo      Foo :: bar
fn(HashMap < String, String >)
Foo :: foo      Foo :: bar
error[E0308]: mismatched types
  --> method-miner\src\lib.rs:10:6
   |
10 |   impl Foo {
   |  ______^
11 | |     fn foo(&mut self, h: HashMap<String, String>) { }
   | |__________^ incorrect number of function parameters
   |
   = note: expected fn pointer `fn(HashMap<String, String>)`
                 found fn item `for<'r> fn(&'r mut Foo, HashMap<String, String>) {Foo::foo}`

Code

//!

use miner_macro::mine_methods;

use std::collections::HashMap;

struct Foo;

#[mine_methods] // <-- Generates a `foo_methods` module in the parent module
impl Foo {
    fn foo(&mut self, h: HashMap<String, String>) { }
    fn bar(&mut self, h: HashMap<String, String>) {}
}



#[cfg(test)]
mod tests {
    // use super::*;

    #[test]
    fn it_works() {

        println!("methods:");
        for method in super::foo_methods::METHODS.iter() {
            println!("  {method}");
        }
        todo!()
    }
}

Yes, edge case 1: haven't expand self family to the real implementor.

Edit: fixed now :slight_smile:

1 Like

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.