When to use Struct instead of Hash in Ruby?
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, whereasFoo = Struct.new(:x); Foo.new(42)==Foo.new(42)
is true). - The
to_a
method for structs returns an array of values, whileto_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 doFoo.new(1,2,3)
to create an instance ofFoo
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.
Comments
-
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 almost 14 yearsYour other points are all correct (so +1 for that), but
Struct#==
works differently from what you explained when you actually store the result ofStruct.new
as opposed to calling it twice with the same arguments. -
sepp2k almost 14 years@MarkRushakoff: If I do
Foo = Struct.new(:x); Bar = Struct.new(:x)
and then doFoo.new(42) == Bar.new(42)
I will get false. If I doFoo.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 almost 14 yearsI 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 almost 10 yearsI 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 over 8 yearsMaybe 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 about 7 yearsIs 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 thatFoo = Struct.new(:z, :a, :b)
, can i rely thatFoo.new(1, 2, 3).to_a[0] == 1
? Specifically, can i rely thatto_a[0]
is always the value of the:z
? -
sandre89 about 7 yearsI 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 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 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 bystruct[variable]
. -
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 almost 7 yearsYou can do
class Point << Hash
and all will be kind of the same. -
Shimu over 5 yearsI 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 almost 4 yearsWith 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.