Hash のハッシュ値

Ruby はいつまで立っても知らない仕様・挙動に出会える味わい深い言語です。
今回は ruby 1.8 の Hash のハッシュ値が (内容物に関係なく) インスタンスだけに依存して決まることを知りました。知らなかったのかよ、というツッコミはなしで。

# 1.8
x = { :foo => :bar }
y = { :foo => :bar }
h = { x => 42 }
p h[x]  #=> 42
p h[y]  #=> nil

ひどい落とし穴に見えますが、今まで実際に引っかかったことがなかったので、書きそうで書かないコードなんでしょう。Ruby のマニュアルの Hash の項目に「キーとして与えたオブジェクトの内容が変化し、メソッド hash の返す値が変わるとハッシュから値が取り出せなくなりますから、Array、 Hash などのインスタンスはキーに向きません。」とありますが、Hash のインスタンスは問題ないような気がします。
ただし、1.9 では内容物のハッシュ値から計算するようになっています。

# 1.9
x = { :foo => :bar }
y = { :foo => :bar }
h = { x => 42 }
p h[x]  #=> 42
p h[y]  #=> 42

従って、1.9 では先ほどのマニュアルの通り、キーになっているハッシュを更新した場合に Hash#rehash が必要でした。

# 1.9
x = { :foo => :bar }
h = { x => 42 }
p h[x]         #=> 42
x[:baz] = :qux # キーの更新 (ハッシュ値も変わる)
p h[x]         #=> nil
h.rehash
p h[x]         #=> 42

ハッシュ自身をキーにすると不思議な気分になります。

# 1.9
h = {}
h[h] = 1
p h[h]  #=> nil

これも h[h] = 1 の実行時の h のハッシュ値と、p h[h] の実行時の h のハッシュ値が異なるためにこういうことになります。

# 1.9
h = {}
h[h] = 1
h.rehash
p h[h]  #=> 1

h[h] の代入を複数回実行するとさらにカオスで、二回 rehash する必要があります。

# 1.9
h = {}
h[h] = 1; p h  #=> {{...}=>1}
p h[h]         #=> nil
h[h] = 2; p h  #=> {{...}=>1, {...}=>2}
p h[h]         #=> nil
h[h] = 3; p h  #=> {{...}=>1, {...}=>2, {...}=>3}
p h[h]         #=> nil
h.rehash; p h  #=> {{...}=>3}
p h[h]         #=> nil
h.rehash; p h  #=> {{...}=>3}
p h[h]         #=> 3


ついでに変なコード。

h1 = {}
h2 = {}
h1[h1] = 1
h2[h2] = 1
h1.rehash
h2.rehash
p h1 == h2  #=> 1.8 では false 、1.9 では SEGV

1.9 の SEGV は多分スタックあふれなので仕様っぽいですが、1.8 が false を返すのは謎です。true か stack level too deep を期待したのに。そういえば Ruby の等価性もちゃんとわかってないなあ。追記: SEGV しなくなったようです。