Reputation: 143
I've written a program to process my financial transactions but it's starting to run a little slower now that I'm adding more data to it. I decided to write it in Rust. I'm fluent in JS, TS, Python, bash/shell scripting. I need to feed the entire history into the application at this time. Currently my program is single-threaded. My thought is that if I use multi-threading strategically I should be able to cut the run-time down.
Here is how I attempted to implement multi-threading:
for row in lines[1..].iter() {
thread::spawn(|| {
process_transaction(row, &rules)
});
}
Rules is a hashmap that looks like this.
type CustomRule = Box<dyn Fn(&Transaction) -> &'static str>;
type RuleHashMap = HashMap<&'static str, CustomRule>;
Row is a Transaction structure w/ stuff and some functions implemented into it. (Regex, gt/lt matching) The key will be a regex string and the value will be that custom function. This custom function has to be put in a Box because it's size needs to go onto the heap? The idea is that I should be able to just quickly iterate over a set of regex patterns then run the corresponding additional logic needed to process that transaction. I am not mutating Transaction or Rules in any way and I'm only printing a result. Here's an example rule:
rules.insert(r"(?i)7-ELEVEN|EXXONMOBIL|CIRCLE K|SUNOCO|SHEETZ|A-PLUS|RACEWAY|SHELLSERVICE|Shell SERVICE|QUICK NEASY|QUICK N EASY|FAS MART|BP|ROYAL MART|CITG|gas|wawa", Box::new(|t:&Transaction|{
match t.less_than(15.0) {
true => "expenses:convience",
false => "expenses:business:gas"
}
}));
The compiler suggested I update the type to implement Send
type CustomRule = dyn Fn(&Transaction) + Send + 'static;
I added that but now it says it does not know the size of the HashMap at compile time. I get this message from the compiler
= help: the trait `Sized` is not implemented for `(dyn for<'r> Fn(&'r Transaction) + Send + 'static)`
note: required by a bound in `HashMap`
What is this? I'm new to lower level programming like this and I want to understand what is actually happening versus just copying blindly. Especially when playing w/ threads. Was putting that custom rule function into a Box<>
type the wrong move? Am I making this more complicated than needed.
Upvotes: 0
Views: 73
Reputation: 15012
If all of your closures are "pure" functions that don't capture any state, then you can just store fn pointers in your map instead:
let mut rules: HashMap<&'static str, fn(&Transaction) -> &'static str> = HashMap::new();
rules.insert(r"some long regex", |t: &Transaction|{
"expenses:convience"
});
You say
The idea is that I should be able to just quickly iterate over a set of regex patterns then run the corresponding additional logic needed to process that transaction.
If you're actually iterating, why even use a HashMap
? Just use a Vec
or array of tuples like so:
let mut rules: [(&'static str, fn(&Transaction) -> &'static str); 1] = [
(r"some long regex", |t: &Transaction|{
"expenses:convience"
}),
];
You probably also should compile the Regexs only once, rather than compiling and running them every time. I recommend putting them in statics with once_cell::sync::Lazy
.
Upvotes: 1