モチベーション
rubyの公式ドキュメントを見ても「シンボルは内部的に数値として扱っています」「テーブルを使ってGCをしています」としか書いておらず、具体的にどんなテーブルなのかまた、シンボルはどんな数値なのかという情報が全くわからなかったので調べました。
概要
シンボルを管理しているテーブルはハッシュオブジェクトを管理しているものと同じハッシュテーブルです。
そして、シンボルはハッシュ化されて管理されています。その詳しい仕組みを解説します
Symbol.all_symbolメソッドとは
まずはじめに次のプログラムを実行してみましょう。
a = Symbol.all_symbols.dup
a.any?{|val| val == :hello_world } # ここでhello_worldというシンボルが作成される
# => false
a = Symbol.all_symbols.dup
a.any?{|val| val == :hello_world }
# => true
irbで実行してみるとわかる通り、Symbol.all_symbolsは新しいシンボルが作成されるたびに実行結果が変わります。
さらに
(:hoge.object_id == :hoge.object_id) == true
であることから、シンボルがシングルトンオブジェクトであることがわかります。
この実行結果から、:hogeというシンボルを使った際に次のような挙動をしていることが推測できます。
- :hogeというオブジェクトをテーブル内で検索する
- :hogeが見つかった場合、そのオブジェクトを返す
- :hogeが見つからなかった場合、新しくオブジェクトを作成しテーブルに登録をし、作成したオブジェクトを返す
という挙動をしてるのが推測できます
シンボルを管理しているテーブルについて
下のソースコードはrubyのプロジェクトの一部です。
static VALUE
lookup_str_sym(const VALUE str)
{
st_data_t sym_data;
if (st_lookup(global_symbols.str_sym, (st_data_t)str, &sym_data)) {
VALUE sym = (VALUE)sym_data;
if (DYNAMIC_SYM_P(sym)) {
sym = dsymbol_check(sym);
}
return sym;
}
else {
return (VALUE)0;
}
}
このlookup_str_symという関数は名前から推測すると、文字列オブジェクトを引数にとってシンボルを返す関数っぽいですよね。lookupというの名前からしてテーブルから検索するっぽいですね。
st_lookupは第一引数がデータを格納したテーブル、第2引数がキー、第3引数が検索に成功した時にデータを書き込むためのポインタです。検索に失敗したら0、成功したらsym_dataにシンボルオブジェクトを書き込んで1を返します。
st_lookupの第一引数のテーブルの正体は何でしょう?それを知るためにglobal_symbols.str_symの正体を知る必要があります。
同じファイルの中にInit_symという関数があるので、その中を見てみましょう。
global_symbols.str_sym = st_init_table_with_size(&symhash, 1000);
st_init_table_with_sizeはハッシュ値のキーとバリューの管理にも使われています。ハッシュテーブルです。
シンボルが数値と等価という解説している記事を何回かみたことがあると思いますが、その数値の正体はハッシュ値だったのです。何のハッシュ値かというと文字列のハッシュ値ですね。
その証拠にsymhashの定義を見てみましょう。
static const struct st_hash_type symhash = {
rb_str_hash_cmp,
rb_str_hash,
};
これはst_init_table_with_sizeの引数にも使われています。なので、この構造体の中身を使って検索していることが推測できます。
global_symbols.str_symの正体は文字列オブジェクトをキーにしてシンボルを返すハッシュテーブルだったのです
最後に
日本語が不自由・説明がわかりにくいなどあると思うので、気軽に編集リクエストを送ってください