Call member functions depending on a string parameter

Hi all,

I have a coding problem and am stuck. I have a struct and want to call its associated functions depending on a string that I get from somewhere else. I know that I could have a big match statement that calls the correct function, but I don't like that this has so much boilerplate.

My idea was to use a std HashMap to pair a string with the respective member function. After some fiddling with the lifetime parameters I got it to compile, but now I'm getting a "cannot borrow as mutable more than once at a time" and I don't understand why. Here is the code:

use std::collections::HashMap;

type MemberType<'a, 'b> = fn(&'b mut SomeStruct<'a, 'b>, i32);
struct SomeStruct<'a, 'b> {
    ref_val: &'a i32,
    value: i32,
    function_map: HashMap<String, MemberType<'a, 'b>>
}

impl<'a, 'b> SomeStruct<'a, 'b> {
    fn change_value(&mut self, new_value: i32) {
        println!("Value: {}", self.value);
        self.value = new_value;
        self.ref_val = &8;
    }

    fn change_value_2(&mut self, new_value: i32) {
        println!("Value2: {}", self.value);
        self.value = new_value;
    }

    fn call_function(self: &'b mut SomeStruct<'a, 'b>, function_name: String) {
        let function = self.function_map.get(&function_name).unwrap();
        function(self, 1);
    }

    fn increment(&mut self) {
        self.value += 1;
    }
}

fn main() {
    let number = 5;
    let mut test_map: HashMap<String, MemberType> = HashMap::new();

    test_map.insert("change_value".to_string(), SomeStruct::change_value as MemberType);
    test_map.insert("change_value_2".to_string(), SomeStruct::change_value_2 as MemberType);

    let mut some_struct = SomeStruct {
        ref_val: &number,
        value: 0,
        function_map: test_map
    };

    some_struct.increment();
    some_struct.increment();
    
    some_struct.call_function("change_value".to_string());
    some_struct.call_function("change_value_2".to_string());
    println!("Number: {}", number);
}

The error occurs on the second call to call_function.

Some context: I gave SomeStruct 2 lifetime parameters, because I didn't find another way to cast the Member functions into something that can be put into a HashMap. If I don't explicitly specify a type for the Hashmap, the compiler will treat SomeStruct::change_value as a function item and not a function pointer. I added increment to show that mutably borrowing works normally otherwise and the print statement to make sure that the lifetime of the borrowed value is long enough.

Any help is appreciated!

Works on the playground if we elide 'b. This is by no chance better than using a simple match on a string though.

1 Like

Thanks for the quick reply! It works!!
Why would you say doing this is worse than the match?

I don't like keeping the hash map around. It seems like unnecessary runtime overhead to me, as with using the match, we can get rid of that completely. Also I think using the hash map doesn't remove any boilerplate from your code, it rather adds to it (just in a different place, i.e. where you have to construct it) and makes your code more confusing in my opinion.

1 Like

I think you are right about the confusing part. It's probably harder to understand what's going on if you don't know already. In my case, the HashMap is already doing other stuff, so I would just add this functionality. Which might even be more confusing :smile:

Do you understand why the code I tried before doesn't compile? I don't understand that at all

You had &'b mut T<'b>, which is a type that's borrowed for the entirety of its lifetime (by invariance of mutable references). It's a common beginners' anti-pattern that prevents the value from ever being used again.

3 Likes

Ah, I see. Thanks a lot !!