with_indifferent_accessはなぜ文字列キーを使うのか?

前回からの続き。

  • ところで、最近のRubyは{abc:123}({:abc => 123}と同等)のようなハッシュ定義も可能になり、間違いなくシンボルキーが多用される時代なのに...

with_indifferent_accessは、なぜ文字列キーを使うのか?

  • シンボルは無駄にオブジェクトを生成しない。
    • コード中に:fooがいくつあっても、たった一つの:fooオブジェクトを参照するだけ。
irb(main):001:0> :foo.object_id
=> 538568
irb(main):002:0> :foo.object_id
=> 538568
irb(main):003:0> :foo.object_id
=> 538568
    • オブジェクトIDがすべて538568。同じである。
  • 一方、文字列は何度も異なるオブジェクトを生成する。
    • コード中に"foo"が出現する度に、異なる"foo"オブジェクトを生成している。
irb(main):004:0> 'foo'.object_id
=> 70228655214520
irb(main):005:0> 'foo'.object_id
=> 70228655232480
irb(main):006:0> 'foo'.object_id
=> 70228655227360
    • オブジェクトIDの下6桁が、すべて違っている。
  • 以上のことからも、シンボルの方が無駄なことをせずに良さそうなのだが、なぜ文字列キーが使われるのか?
  • 実は、この無駄のないシンボルの性質が、逆に弱点となる場合もあったのだ。
  • シンボルはたった一つのオブジェクトしか生成しない。
  • そして、一度生成されたシンボルは、後の再利用のためにメモリ中にずっと残る。
  • つまり、GCガベージコレクション*1)の対象にならないのだ。
  • 一方、文字列はオブジェクトを繰り返し生成する。
  • しかし、使われなくなるとGCされ、メモリは解放されるのだ。
  • Webアプリケーションのような不特定な外部入力を受け取って、ハッシュに変換して処理する状況では、
  • GCされるかどうかの違いによって、シンボルは弱点になってしまうこともあるらしい。
  • GCされないということは、多分もう使われないシンボルであっても、rubyプロセスを再起動するまでずっと残り続けることになる。
  • シンボルには、無駄にオブジェクトは生成しないけれど、一旦生成されるとずっとメモリを占有し続ける、という性質があったのだ。
  • そうは言っても、今どきのサーバーはメモリも潤沢だし、たかがシンボルごときのメモリ占有によって、問題になるような影響は出ないんじゃないか?と思ってしまう。
  • 善意のユーザーが想定の範囲内で利用する場合においては、たぶんメモリ不足のような問題は発生しない。
  • ところが、悪意のあるユーザーが何らかの攻撃を仕掛けてきたら、目論見どおりメモリ不足になってしまうことがあるらしい。
  • そのような悪意のある攻撃に対抗するために、with_indifferent_accessは文字列キーを選択していたのだ。
  • indifferentなハッシュは、その内部は文字列キーに統一されている。
  • でもアクセスする時には、シンボル・文字列どちらのキーも使える。
irb(main):001:0> require 'active_support/all'
=> true

irb(main):002:0> h={abc:123}.with_indifferent_access
=> {"abc"=>123}

irb(main):003:0> h[:abc]
=> 123

irb(main):004:0> h[:efg]=456
=> 456

irb(main):005:0> h
=> {"abc"=>123, "efg"=>456}

参考ページ

素晴らしい情報に感謝です!

シンボルの脆弱性
シンボルの未来

*1:不要になったメモリ領域を解放する処理のこと。