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
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.
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.
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.
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...