Ruby におけるハッシュ (Hash) と構造体 (Struct) の使い分け

More than 1 year has passed since last update.

概要

Ruby ではハッシュがよく使われるが、実は構造体も使える。

# ハッシュ
person = {name: "Taro", age: 16}
p person[:name]

# 構造体
Person = Struct.new(:name, :age)
person = Person.new("Taro", 16)
p person.name

どう使い分けるとよいのだろうか?

参考

ポイント1: ハッシュは動的、構造体は静的

ハッシュは存在しない要素を参照したり、要素をあとから追加できます。一方、構造体は初めに定義した要素しか扱えません。

# ハッシュ
person = {name: "Taro", age: 16}
person[:tel] # => nil
person[:sex] = "man" # 要素が追加される

# 構造体
Person = Struct.new(:name, :age)
person = Person.new("Taro", 16)
person.tel # => NameError: undefined method 'tel'
person.sex = "man" # => NameError: undefined method 'sex='

この特性ゆえ、実行時まで属性名が決まらないような場合は、ハッシュの方がきれいに対応できます。

key = ARGV[0]

# ハッシュ
p some_hash[key] # => なんとかなる

# 構造体
p some_struct.key # => ムリ
p some_struct.send(key) # => なんとかなるけど気持ち悪い

ポイント2: 等価の基準が違う

構造体は、たとえ内容が全く同じでも、元になった構造体自体が違う場合、等価になりません。

Person = Struct.new(:name, :age)
person = Person.new("Taro", 16)

Robot = Struct.new(:name, :age)
robot = Robot.new("Taro", 16)

p person == robot # => false. 越えられないカベ

ポイント3: to_a の挙動が違う

ハッシュの方は key-value のペアになった配列が得られます。

# ハッシュ
person.to_a
=> [[:name, "Taro"], [:age, 16]]

# 構造体
person.to_a
=> ["taro", 16]

ポイント4: 構造体はインスタンス生成に属性名がいらない

属性名が長くなりがちなときや、インスタンスをいくつも作るときにスッキリ。

# ハッシュ
person1 = {name: "Taro", age: 16, most_favorite_movie_ever_seen: "Toy Story"}
person2 = {name: "Jiro", age: 17, most_favorite_movie_ever_seen: "Toy Story 2"}
person3 = {name: "Maro", age: 18, most_favorite_movie_ever_seen: "Toy Story 3"}

# 構造体
Person = Struct.new(:name, :age, :most_favorite_movie_ever_seen)
person1 = Person.new("Taro", 16, "Toy Story")
person2 = Person.new("Jiro", 17, "Toy Story 2")
person3 = Person.new("Maro", 18, "Toy Story 3")

結論と感想

ポイント1の違いが一番大きいでしょう。参考リンク先では、例えば「文章中に出てくる単語をキー、その出現回数をバリューにしたいとき」のような動的な処理のときはハッシュがよいだろう、と結論付けられています。私もそう思います。

個人的に感じている構造体の便利ポイントは、単純に「ドット演算子が使える」ことです。これによって、でかいオブジェクトを返してくるメソッド(例えば open など)のスタブ化が簡単にできます。

hoge.rb
require "open-uri"

class Hoge
  def call_api
    response = open "http://something-api/"
    response.read if response.status[0] == "200"
  end
end
hoge_spec.rb
it '接続成功時は "Success" を返す' do
  hoge = Hoge.new

  # read と status に応答できるオブジェクトを即席で作る
  dummy = Struct.new(:read, :status).new('{"result": "Success"}', ["200", "OK"])
  # dummy を返すように open メソッドをスタブ化
  allow(hoge).to receive(:open).and_return(dummy)

  expect(hoge.call_api).to eq '{"result": "Success"}'
end
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.