When to use Struct instead of Hash in Ruby?

19,702

Solution 1

Structs differ from using hashmaps in the following ways (in addition to how the code looks):

  • A struct has a fixed set of attributes, while you add new keys to a hash.
  • Calling an attribute that does not exist on an instance of a struct will cause a NoMethodError, while getting the value for a non-existing key from a hash will just return nil.
  • Two instances of different structs will never be equal even if the structs have the same attributes and the instances have the same values (i.e. Struct.new(:x).new(42) == Struct.new(:x).new(42) is false, whereas Foo = Struct.new(:x); Foo.new(42)==Foo.new(42) is true).
  • The to_a method for structs returns an array of values, while to_a on a hash gets you an array of key-value-pairs (where "pair" means "two-element array")
  • If Foo = Struct.new(:x, :y, :z) you can do Foo.new(1,2,3) to create an instance of Foo without having to spell out the attribute names.

So to answer the question: When you want to model objects with a known set of attributes, use structs. When you want to model arbitrary use hashmaps (e.g. counting how often each word occurs in a string or mapping nicknames to full names etc. are definitely not jobs for a struct, while modeling a person with a name, an age and an address would be a perfect fit for Person = Struct.new(name, age, address)).

As a sidenote: C structs have little to nothing to do with ruby structs, so don't let yourself get confused by that.

Solution 2

I know this question was almost well-answered, but surprisingly nobody has talked about one of the biggest differences and the real benefits of Struct. And I guess that's why somebody is still asking.

I understand the differences, but what's the real advantage to using a Struct over a Hash, when a Hash can do the same thing, and is simpler to deal with? Seems like Structs are kind of superfluous.

Struct is faster.

require 'benchmark'

Benchmark.bm 10 do |bench|
  bench.report "Hash: " do
    50_000_000.times do { name: "John Smith", age: 45 } end
  end

  bench.report "Struct: " do
    klass = Struct.new(:name, :age)
    50_000_000.times do klass.new("John Smith", 45) end
  end

end

# ruby 2.2.2p95 (2015-04-13 revision 50295) [x64-mingw32].
#                 user     system      total        real
# Hash:       22.340000   0.016000  22.356000 ( 24.260674)
# Struct:     12.979000   0.000000  12.979000 ( 14.095455)

# ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin11.0]
# 
#                  user     system      total        real
# Hash:       31.980000   0.060000  32.040000 ( 32.039914)
# Struct:     16.880000   0.010000  16.890000 ( 16.886061)

Solution 3

One more main difference is you can add behavior methods to a Struct.

 Customer = Struct.new(:name, :address) do

  def greeting; "Hello #{name}!" ; end

end

Customer.new("Dave", "123 Main").greeting  # => "Hello Dave!"

Solution 4

From the Struct documentation:

A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.

On the other hand, a Hash:

A Hash is a collection of key-value pairs. It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index. The order in which you traverse a hash by either key or value may seem arbitrary, and will generally not be in the insertion order.

The main difference is how you access your data.

ruby-1.9.1-p378 > Point = Struct.new(:x, :y)
 => Point 
ruby-1.9.1-p378 > p = Point.new(4,5)
 => #<struct Point x=4, y=5> 
ruby-1.9.1-p378 > p.x
 => 4 
ruby-1.9.1-p378 > p.y
 => 5 
ruby-1.9.1-p378 > p = {:x => 4, :y => 5}
 => {:x=>4, :y=>5} 
ruby-1.9.1-p378 > p.x
NoMethodError: undefined method `x' for {:x=>4, :y=>5}:Hash
    from (irb):7
    from /Users/mr/.rvm/rubies/ruby-1.9.1-p378/bin/irb:17:in `<main>'
ruby-1.9.1-p378 > p[:x]
 => 4 
ruby-1.9.1-p378 > p[:y]
 => 5 

In short, you would make a new Struct when you want a class that's a "plain old data" structure (optionally with the intent of extending it with more methods), and you would use a Hash when you don't need a formal type at all.

Share:
19,702
TK.
Author by

TK.

Looking for a job.

Updated on June 21, 2022

Comments

  • TK.
    TK. about 2 years

    I don't have much programming experience. But, to me, Struct seems somewhat similar to Hash.

    • What can Struct do well?
    • Is there anything Struct can do, that Hash cannot do?

    After googling, the concept of Struct is important in C, but I don't know much about C.

  • Ralph Sinsuat
    Ralph Sinsuat almost 14 years
    Your other points are all correct (so +1 for that), but Struct#== works differently from what you explained when you actually store the result of Struct.new as opposed to calling it twice with the same arguments.
  • sepp2k
    sepp2k almost 14 years
    @MarkRushakoff: If I do Foo = Struct.new(:x); Bar = Struct.new(:x) and then do Foo.new(42) == Bar.new(42) I will get false. If I do Foo.new(42) == Foo.new(42) I will get true. And if you read carefully, that's exactly what I said (Two instances of different structs").
  • Ralph Sinsuat
    Ralph Sinsuat almost 14 years
    I see what you mean. It wasn't clear to me because you didn't contrast it with an explanation that equality works as expected when using the same Struct type.
  • rcd
    rcd almost 10 years
    I understand the differences, but what's the real advantage to using a Struct over a Hash, when a Hash can do the same thing, and is simpler to deal with? Seems like Structs are kind of superfluous.
  • user1934428
    user1934428 over 8 years
    Maybe this is of interest: I rerun your benchmark once with Cygwin Ruby (2.2.3p173) and got for the user times 62.462 for the Hash and 19.875. Then I rerun it with JRuby 1.7.23 on JVM 1.7 and got 8.401 for the Hash and 8.701 for the Struct. While the speed advantage is big on Ruby, both seem to have same speed under JRuby.
  • igrek
    igrek about 7 years
    Is it guaranteed that struct's to_a method always returns an array of values that has exactly the same order as initial struct defined? i.e. given that Foo = Struct.new(:z, :a, :b), can i rely that Foo.new(1, 2, 3).to_a[0] == 1? Specifically, can i rely that to_a[0] is always the value of the :z?
  • sandre89
    sandre89 about 7 years
    I think this is one huge difference that would justify Struct vs Hash. Since by Rails convention you shouldn't have two classes in the same ruby file (because of autoloading concerns), many times a Struct is a great way of creating a class replacement that acts as a presenter/decorator.
  • 3limin4t0r
    3limin4t0r almost 7 years
    @igrek I know it's an old question, but the answer is yes. The array is of the same order as the order you passed in attribute names.
  • 3limin4t0r
    3limin4t0r almost 7 years
    @sepp2k Your second point isn't entirely valid. You can use the bracket way to access attributes. If struct = Struct.new(:name, :age) than I can still access the attributes by struct[variable].
  • Nakilon
    Nakilon almost 7 years
    "while getting the value for a non-existing key from a hash will just return nil." -- you can use Hash#fetch
  • Nakilon
    Nakilon almost 7 years
    You can do class Point << Hash and all will be kind of the same.
  • Shimu
    Shimu over 5 years
    I rerun this simple benchmark with a newer CRuby version (2.5) and the Struct is still faster. Not as much as it used to be, but still significantly faster (12.19sec vs 8.28 sec on my machine)
  • Kirill
    Kirill almost 4 years
    With Ruby 2.6.6 Hash (5.9 sec) is faster than Struct (9.4 sec) on my MacBook. With Ruby 2.3.1 Struct is 3 times faster than hash.