Calling functions with various arguments from a HashMap

Dear All,

Here is the problem i still can't solve and asking for your help.
I want to utilize HashMap<String, function or *function> to call functions depending on the String command i receive.
I tried it with function pointers and traits with no luck. Seems that the problem is with inability to provide type for arbitrary function, as Fn() -> () and Fn(u8) -> String are different types. I found a number of possible solutions (many of them dated), but none worked for me:

Is there a way to just store a pointer to the function (say, like in C) and then dereference it with relevant arguments?

Here is my code prototype with options found in the references above:

use std::collections::HashMap;
use std::any::Any;

fn setup_commands() -> HashMap<String,&Fn or &Any> { //this does not work
    let mut commands_map = HashMap::new();
    commands_map.insert(String::from("load"), load()); //here too
    commands_map.insert(String::from("run"), &(run as fn(u32))); //and here
    commands_map.insert(String::from("save"), &(save as fn(u8) -> String)); //and here

    commands_map
}

fn load() {
    println!("command: loading");
}

fn run(x: u32) -> u32 {
    println!("command: running {}", x);
    x
}

fn save(s: String) -> u8 {
    println!("command: saving {}", s);
    42
}

fn main() { //also does not work
    let commands = setup_commands();
    let function_to_run = *(commands.get("run").unwrap());
    function_to_run(10); //should produce > command: running 10
}

Many thanks for your help in advance!

If arbitrary functions can be inserted with arbitrary names, how are you going to know what to pass when you extract them later? Or in terms of your example, how do you know to pass a u32 to the function associated with "run"?

Your sketch can probably be made to work with downcasting, but if we're talking about a fixed set of commands, something more strongly typed than a HashMap could be a better approach.

2 Likes

Emphasizing this point, two of your three functions didn't line up with your function pointer casts in your sketch.

Anyway, though I don't really recommend it, here's one way to save and downcast function pointers of differing type.

2 Likes

Quinedot, thank you so much for the answer!

It works. Realized i did not consider Box...
However, for general understanding and learning Rust, may i ask you more about what you wrote:

  1. Why don't you recommend the solution you provided? What is wrong with it?
  2. What would be your solution then? You mentioned " something more strongly typed than a HashMap could be a better approach" - what would this be?
    Making hundreds or a thousand of match arms does not look like a good solution to me.
  3. May be i could wrap the function pointers into some struct command, and then just use HashMap<String, struct> instead? This does not change you criticism of the general idea of using function pointers though...

Again, many thanks in advance!

Every place where commands are inserted or called will have to agree on the underlying types (function paramters and return type) for the commands to be of any use, but you've erased all the types so there's nothing in the language ensuring this happens. Instead you'll have to catch it at runtime (or ignore it at runtime and have potentially confusing behavior).

E.g. here's the playground when main uses the signature you had in your sketch. The downcast fails because the types don't match, so it never calls the function.

I don't know enough about your use case to posit a best approach. But if you have thousands of function signatures, and any command could have any function signature, you'll still have thousands of arms with this approach, because you'd have to try and downcast thousands of times.

It might work alright for setting if you had some sort of privacy barrier so you could only use one function signature for a given command, but you can't have as nice a barrier for calling unless you again handle every possible function signature. So users of the command hash will still have to get it right, or try a ton of downcasts. You'd also have to handle every command in the setter, unless most of them use the same default signature I guess, so at that point why use Strings anyway?

But if you have types, you could use a trait to make sure signatures match at compile time, and a method to make downcasting mismatches easier to avoid at the use site. Example.

I don't know that there really is a better way for that part if you're going to stick with type erased function pointers; at least downcasting doesn't require getting something unsafe right.

1 Like

Quinedot, thank you again!
You are so very right - i did not realize that fn() -> (something ) problem at first.
The new solution looks really cool and this seems to be exactly what i actually wanted to do.
From the first glance i don't understand some parts of the code, but shall sit down and study it now...

Hello, Quinedot!

I studied the code now. There is still a couple things with how you downcast function returns and use macro that i could not understand completely (well, it is beyond the Rust book and i shall have to read more elsewhere). Nonetheless, i got the main idea.

Now, there is a problem with enums - i shall have to create another HashMap to convert String commands into enums. I shall try to implement this myself and shall report the results here. In case it will work - this is going to be the final (but may be not the elegant one?) solution of my problem.

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.