1

I don't understand the difference between struct and class equality check. Since both Struct and Class gets their #hash from Kernel but they seem to behave differently.

I know that instance.hash will produce a different result for each class instance. Struct instance has different ancestors [Customer, Struct, Enumerable, Object, Kernel, BasicObject] compare to Class instance [Foo, Object, Kernel, BasicObject]. What really caused each Class instance to have a different hash number to othe

Customer = Struct.new(:name, :phone, :address) do

end

class Foo
  def initialize(the_name, phone, address)
    @name = the_name
    @phone = phone
    @address = address
  end
end


str_a = Customer.new('bond', 'ring', 'address')
str_b = Customer.new('bond', 'ring', 'address')

foo_a = Foo.new('bond', 'ring', 'address')
foo_b = Foo.new('bond', 'ring', 'address')

p str_a == str_b #true
p foo_a == foo_b #false

p str_a.hash # 4473040617195177332
p str_b.hash # 4473040617195177332
p foo_a.hash # -3118151143418428190
p foo_b.hash # -1042397847400824657

p str_a.method(:hash).owner #Kernel
p foo_a.method(:hash).owner #Kernel

both Struct and Class use Kernel for its hash_number generation. Why do a different instance of Class produce different hash int but Struct instance would produce the same hash int?

anothermh
  • 9,815
  • 3
  • 33
  • 52
MorboRe'
  • 151
  • 10

1 Answers1

5

I believe the answer you're looking for is found in the Struct documentation

Equality—Returns true if other has the same struct subclass 
and has equal member values (according to Object#==).

Your example has equal member values for str_a and str_b, and they have the same subclass (Customer), so they are equal when compared with ==

Contrast this with the Object documentation

Equality — At the Object level, == returns true only if 
obj and other are the same object. Typically, this method is
overridden in descendant classes to provide class-specific meaning.

In your example, foo_a and foo_b are not the same object (because they're not the same instance)

If you're seeking why these are different, I didn't really answer that question. Just that the behavior is as intended per the docs. They don't actually have the same ID:

pry >> Bar = Struct.new(:name) do; end
=> Bar < Struct
pry >> x = Bar.new
=> #<Struct:Bar:0x7f8ebca47610
        name = nil

pry >> y = Bar.new
=> #<Struct:Bar:0x7f8ebca14058
        name = nil

pry >> x.name = "foo"
=> "foo"
pry >> y.name = "foo"
=> "foo"
pry >> x
=> #<Struct:Bar:0x7f8ebca47610
        name = "foo"

pry >> y
=> #<Struct:Bar:0x7f8ebca14058
        name = "foo"

But, you'll note comparison is based on attributes, rather than the object ID:

pry >> x == y
=> true

Even though the object id's are different:

pry >> x.__id__
=> 70125513489160
pry >> y.__id__
=> 70125513383980
Jay Dorsey
  • 3,563
  • 2
  • 18
  • 24
  • I meant why are the hash int are different for same-valued-Class-instances but same hash int for Struct instances with the same value? – MorboRe' Dec 31 '18 at 23:47
  • I thought the behavior is odd since both Struct and Class inherits #hash from Kernel but they act differently. – MorboRe' Dec 31 '18 at 23:53
  • 1
    They are not the same methods. [`Struct#hash`](https://ruby-doc.org/core/Struct.html#method-i-hash) is a different (overwritten) method from `Kernel#hash` to take into account the different equality rules. Note that an object's `hash` is not strictly used for object equality usually but to identify a key for an object to be used e.g. as a key in a `Hash` object. If two objects have the same `hash` value, they will use the same "slot" in a `Hash` object. – Holger Just Jan 01 '19 at 11:49
  • @MorboRe' the link & explanation posted by Holger is the answer to your hash question specifically. If you toggle source you can see that they're building the hash from (what looks like) the member values. This aligns with why equality is calculated differently (that's how it's meant to work, because it calculates the hash based on values ) – Jay Dorsey Jan 02 '19 at 14:08
  • interesting, I tried Struct.new("N").method(:hash).owner to find out the owner of the #hash method then got return value as Kernel. I ran some test with instance methods from some customized Class but the result of that turns out to be my last modified Class. Therefore I assume #owner returns the last overridden Class as the result. Since Struct inherits from Emuerable. I guess #hash was redefined there because the equality test for Emuerable works the same way. thx for explanation – MorboRe' Jan 03 '19 at 09:50