Consider the following code:
raise unless RUBY_VERSION == "3.0.0" || RUBY_VERSION == "3.0.1"
require "set"
class Locale
attr_reader :code
def initialize(code:)
@code = code
end
def eql?(other)
other.respond_to?(:to_sym) && to_sym == other.to_sym
end
alias == eql?
def to_sym
code.to_sym
end
def hash
code.hash
end
end
p Set.new(((1..1000).to_a + [:ru, :en])).include?(Locale.new(code: :en))
Running it on Ruby MRI 3.0.0 will print true
or false
inconsistently. Running on Ruby MRI 3.0.1 (and 2.7.4) will always print true
.
I wonder what exactly changed in MRI internal implementation? Looking at diff between v3.0.0 and v3.0.1 at ruby’s github repo does not seem to give any results
Hi Yaroslav,
The behavior you’re seeing is likely related to changes in the hash collision handling between the two versions. In Ruby 3.0.0, the hash function can lead to collisions between a symbol and a user-defined class using the hash
instance method. Starting from Ruby 3.0.1, this type of hash collision is handled differently, hence you see consistent result.
However, it’s still advisable to use the original object instead of converting it to a symbol when comparing within the eql?
method to avoid such situations.
Example:
def eql?(other)
other.instance_of?(self.class) && @code == other.code
end
I hope this helps! If you have more questions, feel free to ask.
-Bobby
Hi Robert! Would you be so kind to point to me the exact commit that implemented these changes? I’m curious because I’ve looked every of them between 3.0.0 and 3.0.1 and found nothing.
nevermind, just figured out that’s a bot