I would like to create a set of "products". As you may know, the product is commutative. That is, the pair (2, 3) is equivalent to (3, 2). I tried the following:

Any time you want to work with a pair (set of 2 elements) vs. a tuple (sequence of 2 elements), the trick is to use a tuple (since that's how data structures reason), with an imposed ordering:

#[derive(
Debug,
Clone, Copy,
PartialEq, Eq, // it just works
Hash, // it just works
PartialOrd, Ord, // not really meaningful, but at least it is consistent
)]
pub
struct Pair<T : Ord> (T, T);
impl<T : Ord> Pair<T> {
#[inline]
pub
fn new (x: T, y: T)
-> Self
{
if x < y {
Self(x, y)
} else {
Self(y, x)
}
}
#[inline]
pub
fn as_ordered_tuple (self: &'_ Self)
-> (&'_ T, &'_ T)
{
(&self.0, &self.1)
}
}
assert_eq!(Pair::new(3, 4), Pair::new(4, 3));

In your case, if you want a set where the pairs compare equal based on equality of the product of the two elements, I'd use a newtype using HashMap<u64, Pair<u64>> as the underlying type; or having something like:

Is your intent only to map away the commutativity? Or do you want to treat any pairs with the same product as equal? For example, (2, 15), (3, 10), and (5, 6) all have the same product 30, thanks to the primitive factors (2, 3, 5). The product-based solution will consider these equal.

If you don't want those to be considered equal, then I would do your equality and hashing based on a normalized order -- first compare/hash the minimum of the pair, then the maximum. (Like @Yandros's imposed ordering, whether you do that at construction or during the comparison.)

It would also be ok if the hash used just the product while equality is more precise. Equal values must have the same hash, but unequal values can have the same or different hashes.