Lessons from porting Lucene: Equality

Java and Rust take two different approaches to evaluating equality of objects. Yes on the surface they are trying to accomplish the same thing but the differences may result in double the methods. First let’s get a foundation in equality.

For the purpose of this article there are three types of equality.

  • Reflexive – An object is “equal to” another object. (eg. x==x)
  • Symmetric – An object is “equal to” another object in both directions (eg. x==y && y==x)
  • Transitive – If an object is “equal to” a second object and that object is “equal to” a third object then the first and the third are “equal to” (eg. x==y && y==z then x==z)

Now both Java and Rust break down comparing just symmetric/transitive and all three. This is sometimes referred to as shallow and deep comparisons. Shallow (or flat) compares the values identity and deep compares the fields/values. Java and Rust’s approach to deep comparisons are the same. So when porting from Java to Rust the “Equals”method logic can directly be ported to “PartialEq”, However shallow comparisons are where they differ. Yes they both do shallow, memcmp-like, pointer-comparing versions. The first difference is Java also leverages a overridable hashCode method to provide a unique identifier for an object. Second to that is Rust will fall back onto deep comparisons (partialEQ) in some scenarios. For instance Floating point NaN can’t be equal.

So what does that mean for my porting process? Because I want to maintain some level of backwards compatibility I need to port the hashCode() method even though it is useless to Rust.