0

I'm trying to build a simple RPN calculator, and I've got the basics working. I'd like to make a dispatch table to implement the various calculator functions. If I were doing this in Perl, I would write something like:

my %ops = (
  '+' => sub { +shift + +shift; },
  '-' => sub { +shift - +shift; },
  '*' => sub { +shift * +shift; },
  '/' => sub { +shift / +shift; }
);

or in JavaScript:

let ops = {
    "+": (a, b) => a + b,
    "-": (a, b) => a - b,
    "*": (a, b) => a * b,
    "/": (a, b) => a / b
};

This is what I've tried so far in Rust:

use std::collections::HashMap;

fn main() {
    println!("Going to call +");

    let dispatch = HashMap::new();

    dispatch.insert(String::from("+"), |a, b| a + b);
    dispatch.insert(String::from("-"), |a, b| a - b);

    let plus = dispatch.get(&String::from("+"));
    println!("2 + 3 = {}", plus(2, 3));

    let minus = dispatch.get(&String::from("-"));
    println!("2 - 3 = {}", minus(2, 3));
}

When I try compiling, I get these errors:

error[E0308]: mismatched types
 --> src/main.rs:9:40
  |
9 |     dispatch.insert(String::from("-"), |a, b| a - b);
  |                                        ^^^^^^^^^^^^ expected closure, found a different closure
  |
  = note: expected type `[closure@src/main.rs:8:40: 8:52]`
             found type `[closure@src/main.rs:9:40: 9:52]`
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object

error[E0618]: expected function, found enum variant `plus`
  --> src/main.rs:12:28
   |
11 |     let plus = dispatch.get(&String::from("+"));
   |         ---- `plus` defined here
12 |     println!("2 + 3 = {}", plus(2, 3));
   |                            ^^^^^^^^^^ not a function
help: `plus` is a unit variant, you need to write it without the parenthesis
   |
12 |     println!("2 + 3 = {}", plus);
   |                            ^^^^

error[E0618]: expected function, found enum variant `minus`
  --> src/main.rs:15:28
   |
14 |     let minus = dispatch.get(&String::from("-"));
   |         ----- `minus` defined here
15 |     println!("2 - 3 = {}", minus(2, 3));
   |                            ^^^^^^^^^^^ not a function
help: `minus` is a unit variant, you need to write it without the parenthesis
   |
15 |     println!("2 - 3 = {}", minus);
   |                            ^^^^^

What does "no two closures, even if identical, have the same type" mean? How can I make a HashMap hold a closure, and then call it?

It sounds like using Box would fix it... Like I said, I'm pretty new, and I haven't used Box. How do I get what's in the box out of the box?

Ashton Wiersdorf
  • 1,865
  • 12
  • 33
  • Can you explain what you find confusing about the compiler message: `no two closures, even if identical, have the same type; consider boxing your closure and/or using it as a trait object`? That way, we know how to usefully answer your question. – Shepmaster Jul 17 '18 at 03:14
  • I believe your question is answered by the answers of [Expected closure, found a different closure](https://stackoverflow.com/q/39083375/155423). If you disagree, please [edit] your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Jul 17 '18 at 03:18
  • I experimented with [a few different dispatching patterns](http://hg.mwdiamond.com/rivet), which you might find helpful / interesting. – dimo414 Jul 17 '18 at 03:21
  • @Shepmaster It does indeed look like a similar problem. Thanks for linking to it. I'd appreciate a clearer answer to my specific problem, though. :) I think that'd be valuable not only to myself but anyone looking for "dispatch table". – Ashton Wiersdorf Jul 17 '18 at 03:26
  • `consider boxing your closure and/or using it as a trait object` That's the right answer, right there. Give your hash map an explicit type that all of the closures can agree with. – Silvio Mayolo Jul 17 '18 at 03:32
  • @SilvioMayolo That sounds like it would work. How do I do that though? (I put that at the bottom of the question.) – Ashton Wiersdorf Jul 17 '18 at 03:36
  • If you are looking for the exact syntax, that question has two answers showing syntax. [What is the inferred type of a vector of closures?](https://stackoverflow.com/q/29371914/155423) also does. – Shepmaster Jul 17 '18 at 03:44

1 Answers1

4

There are a few orthogonal issues here. First and foremost, your hashmap is immutable. You use let instead of let mut, which is good practice, but in order to be able to insert into it, we need it to (at least initially) be let mut. If you're planning to modify the hashmap after the initial construction, you may want to let mut the dispatch variable as well.

let dispatch = {
    let mut temp = HashMap::new();
    temp.insert(String::from("+"), |a, b| a + b);
    temp.insert(String::from("-"), |a, b| a - b);
    temp
};

Now you need an explicit type for your hashmap. The two closures you've defined, as far as the compiler is concerned, are of entirely distinct types. However, they are both compatible with fn(i32, i32) -> i32, the type of binary functions on i32 (you can replace i32 with a different numerical type if you wish), so let's make the type explicit.

let dispatch = {
    let mut temp: HashMap<String, fn(i32, i32) -> i32> = HashMap::new();
    temp.insert(String::from("+"), |a, b| a + b);
    temp.insert(String::from("-"), |a, b| a - b);
    temp
};

Finally, HashMap.get returns an std::option::Option, not a direct value, so we need to unwrap it. get returns None if the key isn't found. If this was a large project, we'd handle that error appropriately, perhaps by logging it or telling the user, but for something simple like this, we simply need to use expect, which essentially tells the compiler "Yes, I know this could go horribly wrong. I'm willfully ignoring that fact." which is perfectly fine for our simple example.

let plus = dispatch.get(&String::from("+")).expect("Couldn't find +");
let minus = dispatch.get(&String::from("-")).expect("Couldn't find -");

Complete example

use std::collections::HashMap;

fn main() {
    let dispatch = {
        let mut temp: HashMap<String, fn(i32, i32) -> i32> = HashMap::new();
        temp.insert("+".into(), |a, b| a + b);
        temp.insert("-".into(), |a, b| a - b);
        temp
    };

    let plus = dispatch["+"];
    println!("2 + 3 = {}", plus(2, 3));

    let minus = dispatch["-"];
    println!("2 - 3 = {}", minus(2, 3));
}

Note that you can replace String with &'static str:

let dispatch = {
    let mut temp: HashMap<_, fn(i32, i32) -> i32> = HashMap::new();
    temp.insert("+", |a, b| a + b);
    temp.insert("-", |a, b| a - b);
    temp
};
Boiethios
  • 38,438
  • 19
  • 134
  • 183
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116