What's the right way to execute a function based on a string?

I'm trying a different approach to explaining my question because lots of people refer to XY problems here, so maybe if I am more explicit, it might be more clear.

There seems to be many possible ways to approach this in Rust so I thought I would ask which is the most idiomatic.

I am porting an application from Python. In this application I receive some data from the web. I need to validate some fields but not others.

there is a bunch of functions that validation/transform and return field data. each function named after the field

def clean field_Name(field_value):
    if field_value is invalid:
        #raise an error
    else:
        return field value
...
... rest of clean functions

there is a Python dict (equivalent to Rust HashMap) containing the names of the clean functions

clean_fields_dispatch = {
            "Name": clean_field_Name;
            "Suburb": clean_field_Suburb;
            "City": clean_field_City
            "TaxFileNumber": clean_field_TaxFileNumber;
            "Age": clean_field_Age;
            "JobTitle": clean_field_JobTitle;
            "Country": clean_field_Country;
            "State": clean_field_State;
            "Hobbies": clean_field_Hobbies;
}

example inbound form data dict

form_data = {
            "Name": "John Smith",
            "Suburb": "Carlton",
            "City": "Melbourne",
            "TaxFileNumber": "234252323523523";
            "Age": "33";
}
required_fields_to_validate = ["Name", "Suburb", "City"]
optional_fields_to_validate = ["Hobbies"]

the logic works by looking up the name of the clean function then passing the field value into it.

for field_name in required_fields_to_validate + optional_fields_to_validate:
    # grab the field value
    field_value = form_data[field_name]
    # send the field value to the appropriate clean function
    cleaned_field_value = clean_fields_dispatch[field_name](field_value)
    # replace the unclean data with the clean data in the form
    form_data[field_name] = cleaned_field_value

The key question I have ..... what is the "right" way to create a function dispatch table which is looked up with a string?

I don't know if there's the canonical way to do so, but I would probably reach for an enum plus FromStr or similar, and then match off of the enum value to dispatch.

enum CanSanitize { ... }
impl CanSanitize {
  fn sanitize(&self, String) -> String {
    match *self {
        ...
    }
  }
}

// ...

if let Ok(variant) = field_name.parse() {
  form_data[field_name] = variant.sanitize(field_value);
}

I've found the strum crate useful for the FromStr portion.
https://crates.io/crates/strum

Your solution is very specific to dynamically-typed languages, and there isn't a direct equivalent in strongly-typed language. The right way is just not to solve this problem this way at all. In Rust names of functions and fields don't exist in the program.


You could write something like that:

match field_name {
   "foo" => { form.foo = validate_foo(input)?; },
   "bar" => { form.bar = validate_bar(input)?; },
}

and perhaps generate the whole match with a macro.


If you really need a lookup, you could have HashMap<String, Box<dyn Fn(&str) -> T>>.

However, in this case the T part is going to be tricky. You'd either need it to be an enum like serde_json::Value that can hold string/integer/bool and any other type that any of the functions may need to set, and then "unwrap" it when assigning to a field of a specific type, or use the enum type for the fields too.

Or have separate hashmaps for each return type, and structure your program to do lookup in the right one.

In your solution, is it possible to get the function in the match and execute it under the match?

i.e. (naive and wrong code):

let clean_function = match field_name {
   "foo" => validate_foo,
   "bar" => validate_bar,
}
form.insert(field_name, clean_function(field_value))

If all the values are strings, then a HashMap as suggested by @kornel that has field names for keys and string -> string mapping functions as values would work well. The data could be stored in a HashMap<String, String> too.

Alternatively, if you’d like to parse the data into a strongly typed struct (eg using serde), but still have lists of field processors, one way to do that would be to move the field reading and writing into the processor. You could change the loop to:

for validator in validators {
    validator(&mut data);
}

And have the validators be a Fn(&mut Data). That way all the field types can be different, but the list of validators could still have a simple-ish Vec<Box<dyn Fn(&mut Data)>> type. Well, simple in the sense that they’re more wouldn’t be extra switching on an enum type at least...

Yes, if all functions return the same type.

1 Like