The issue is when you are doing a API and both the tuple and HashSet are some internal implementation you don't want to leak. I basically have to re-rewrite mostly of the code if I want to test if can optimize with a HashSet with tuple.
Do we have any trick to deal with it? Maybe a custom implementation of Partial or Eq?
For context: My case I was playing with a grid of rooms and portals between rooms. So user can check if aportal exist giving 2 coordinates, I would just check a contains in a hashmap with a custom struct that Eq,Ord,Hash of (a,b) == (b,a).
Implement Hash, PartialEq and Eq for dyn Query. When you do this it is important that the implementations are consistent with Hash and PartialEq of your value type, (i32, i32). For tuples this is easy. For more complicated types you may need to create a custom wrapper for the value type so that you can make the implementations agree exactly.
This approach has several potential performance issues which you should be aware of. First and most obviously, it's a total waste to do this with cheaply copied types like i32. It would be more reasonable with strings or more expensive types. Even so, .a() and .b() are indirect calls and you have to make at least 6 of them to look up a member of the set if it's present (2 if it's not). You can reduce the number of virtual calls by designing Query more intelligently. Devirtualization is possible, but in my experience LLVM hardly ever devirtualizes anything in Rust, so I wouldn't count on it.
If the value type is not feasible to clone, this might be a good approach. If performance is a bottleneck, you should profile this solution against other options like HashSet<(Cow<i32>, Cow<i32>)> or using a different data structure entirely.
Another option is to use the raw entry api with no overhead, you can do this on stable via the hashbrown crate. Note: std hashmap/set is implemented in terms of hashbrown.