A method named hash in the main module overrides some object hash method

Given this script

def hash puts "why?" end x = {} x[[1,2]] = 42 

It displays the following

 why? /tmp/a.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError) from /tmp/a.rb:6:in `<main>' 

It seems that the hash function defined in the script overrides Array#hash in this case. Since the return value of my hash method is nil , not Integer , it throws an exception. The following script seems to confirm this

 puts [1,2,3].hash def hash puts "why?" end puts [1,2,3].hash 

Output signal

 -4165381473644269435 why? /tmp/b.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError) from /tmp/b.rb:6:in `<main>' 

I tried to learn the Ruby source code, but could not understand why this was happening. Is this behavior described?

+5
source share
2 answers

You do not override Array#hash , you obscure Kernel#hash by creating Object#hash :

 puts method(:hash) def hash puts "why?" end puts method(:hash) 

What prints:

 #<Method: Object(Kernel)#hash> #<Method: Object#hash> 

Correct it so we can see more:

 def hash puts "why?" super end x = {} x[[1,2]] = 42 

Now the conclusion:

 why? why? 

And no mistakes. Try with x[[1,2,3,4,5,6,7]] = 42 , and instead you will see why? seven times. Once for each element of the array, since the hash method of the array uses hashes of its elements. And Integer#hash does not exist, it inherits its hash method from Object / Kernel , so yours is used.

+4
source

This is due to a kind of hack at the top level of Ruby. Have you ever wondered how this works?

 def foo end p self foo class Bar def test p self foo end end Bar.new.test # no error 

How can two completely different objects ( main and a Bar ) call foo , like calling a private method? The reason is ... it's a private method call.

When you define a method at the top level of your Ruby script, it is included (via Object ) in each object. This is why you can call top-level methods, such as global functions.

But why does this violate only hash and not other common methods? def to_s;end , for example, will not be split into to_s . The reason is that hash is recursive: most class implementations end up referencing Object#hash for their implementations. By overriding this base case, you break it around the world. For other methods, such as to_s , you will not see a global change because it approaches the inheritance chain and is not called.

* the only objects that do not violate this are a few literals, which probably have hardcoded hash values, for example. [] {} "" true , etc.

+3
source

Source: https://habr.com/ru/post/1274076/


All Articles