This line means that there are two allocations during lookup:
match self.values_lookup.get(&(arg1.to_string(), arg2.to_string())) {
I think it would be better if there wasn't any .to_string() involved during lookup. Fixing this doesn't seem so easy. I think it would require to create a custom struct that you store in the BTreeMap.
I faced a similar problem regarding tuples in mmtkvdb and was able to avoid some unnecessary allocations with this StorableRef trait (in my case).
Edit: I just noticed this problem already exists in the OP. So I could/should have replied to the OP instead.
Simply a reminder and an example that you can initialize and pass a value as an argument without extra statements, which is often handy when dealing with initialization. foo() here would be taking a ValuesLookup and doing something with it.
@jbe I noticed that too, and switched to &str in my code.
But it doesn't matter for the general question.
For many people familiar with Rust it's obvious to create impl's for a struct and peform work in the impl, but this is not existing in traditional/ancient languages, and maybe not extensively documented so that people from an ancient computer language can pick up easy.
@simonbuchan My idea was how to initialise a struct with accompanying code to minimise codelines and have it look elegant along the way. I think that when using what you showed, the work is performed in it's own scope? The foo function doesn't return anything, so how does the last line 'a' (without ';') work?
I am not criticising, I am trying to understand.
Ah. So, what I was saying is that let a = ...; foo(a) is the same as let a = ...; foo({ a }), which is the same as foo({ let a = ...; a }). This can be handy to isolate the initialization, or to keep the control flow clearer, without needing to extract the initialization out into a new function.
In programming language terminology, this is saying a block is an expression, which means it has a value and can be used in other expressions. The value is that of the final expression without a semicolon, the same as in a function body. The same is true for other control flow syntax: if a { b } else { c } has either the value b or c, loop { break a } has the value a.
My earlier example, therefore, was simply a statement that you can create a ValueLookup, initialize it, and pass it to foo() all in the same expression, and it's especially a good thing to remember when you're trying to figure out how to create a type that you can initialize nicely.