https://doc.rust-lang.org/std/collections/struct.HashMap.htm...
https://doc.rust-lang.org/std/cmp/trait.Eq.html
https://doc.rust-lang.org/std/cmp/trait.PartialEq.html
There is also a partial ordering relation `PartialOrd` (and `Ord` for complete relations) Floating point values only implement the partial relation as well because of NaN's. This means that it is a bit harder to sort them, though the floating point standard also has a seperate complete comparison relation with some extra rules which you can use instead.
https://doc.rust-lang.org/std/primitive.f64.html#method.tota...
I'd rather panic on NaN comparisons than complicate the fundamental comparison traits. Treat it like integer overflow or array bounds access. We don't have different traits for indexing that might fail or overflow that might happen. There are methods on the trait to handle those cases when you care about them.
But that said, this feels like an XY issue. If you're comparing hashes of floats as keys somewhere you don't want IEEE 754 defined equivalence. You almost certainly want bit equivalence, which means the keys should be type punned to u32/u64. Classic example is memoizing function calls with floats. You don't want to compare the semantic values of the arguments, but the literal bits. For floats that's not the same.
But Rust's built-in floating point types f32 and 64 do not have that property.
If I write a UniqueNumber that just returns false on the == operator, I'd be stupid but the hash map should still work. Java has a separate getHashCode() and equals() method for a reason.
Either provide a hash key override or use the raw bits for deep compares. Custom equality algorithms usually don't make sense for arbitrary key-value stores.
In Rust you can make UniqueNumber, indeed misfortunate::OnewayGreater is such a type. misfortunate::Maxwell even more so.
However of course the HashMap can't meaningfully "work" for this type. Rust promises your program doesn't have Undefined Behaviour despite such a prank, but it might spin forever when you try to insert this type into a map for example, a well defined but undesirable behaviour brought on by your poor choices.
You seem to imagine that Java's hash map doesn't need to compare types, that it can just use getHashCode() -- however because of the Pigeonhole Principle that can't actually work.
And NaN != NaN isn't a "custom equality algorithm" it's literally how the floating point numbers are defined in your CPU.
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
You can't really rely on checking types in a Hashmap anyways because the type you're hashing can have more possible values than the 32bit hash. For example you can have 2 strings that have a hash collision.NaN's work in a Java hashmap because Float.hashCode returns floatToIntBits(float) which normalizes all NaN's to a canonical value.
Rust has a magic unstable trait StructuralEq which means not only are we promised that values of this type can be compared for equality (that's what Eq does) but that comparison will just be a bitwise memory comparison. Lots of useful things are not StructuralEq even if they are Eq
Apparently Java's Floats also do the same canonicalisation for equals(). So in effect in Java although NaN != NaN, once you box it that ceases to be true. This seems like a spectacularly bad idea to me, but presumably it made somebody's awful hack work at one point.
but elsewhere you say, "You are welcome to build a type which has this property and declares that it is Eq". This is exactly the difference between double and Double in Java. double has the `==` operator that isn't reflexive and works as required by IEEE-754 and Double has Double.equals, which is documented[1] to be a reflexive, transitive, symmetric, consistent, and substitutable relation.
[1]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base...
In my opinion, language native hash maps should operate on memory, not on types and their weird implementations. Negative zero and positive zero are defined as different values but are mathematically equal; however, math functions may (and in the case of C, do) behave differently depending on which one you use. Even in higher level languages such as Java sorting gets affected by the presence of negative and positive zero using min/max operators.
If two values claim equality but do effectively alter program behaviour, I consider their equality operation in the context of memory operations such as hash maps to be buggy. There are good reasons to rely on the equality operators, but I do not think such behaviour should be the default.
The fundamental comparisons are complicated period. Pretending they’re not and hoping you don’t hit the branches that crash is a terrible idea for writing reliable software.
For everything where the comparisons aren’t complicated (like integers), Rust is easy. For things where the comparisons are complicated, Rust makes you acknowledge this reality and deal with it. This is correct behavior.
Drain is interesting, because it resolves the other problem if things are in the collection which can never be retrieved from it. Drain gives you each of the things in the collection, one at a time, to do with as you wish, the collection is now empty. So this means even if the collection has a dozen SillyNonsenses in it, all of which insist they're not the one you were looking for whenever you go looking for a SillyNonsense in the collection, you get all twelve of them out in Drain, and can examine them as you wish.
Of course if you just wanted to fish one thing out and then throw the rest away, you can do that, once you drop the result of Drain the rest of the things being drained are dropped.
(I'm pretty sure I recognize your username as someone with way more rust experience than me. For the crowd an implementation of Eq for floats that used the standard comparison operators would be "incorrect", which is why the stdlib doesn't provide one)
But if you have put something daft in a hash map, drain() should definitely get it back out, so there's that.