81

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.

warder57
  • 350
  • 5
  • 16
TK.
  • 27,073
  • 20
  • 64
  • 72

5 Answers5

111

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.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • Your other points are all correct (so +1 for that), but [`Struct#==`](http://ruby-doc.org/core/classes/Struct.html#M000890) 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. – Mark Rushakoff Jul 18 '10 at 13:13
  • @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"). – sepp2k Jul 18 '10 at 13:16
  • 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. – Mark Rushakoff Jul 18 '10 at 13:25
  • 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. – rcd Jul 22 '14 at 22:57
  • 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`? – igrek Apr 03 '17 at 14:49
  • 1
    @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 Sep 19 '17 at 14:27
  • @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]`. – 3limin4t0r Sep 19 '17 at 14:29
  • "while getting the value for a non-existing key from a hash will just return nil." -- you can use `Hash#fetch` – Nakilon Sep 25 '17 at 10:33
52

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)
Community
  • 1
  • 1
Quv
  • 2,958
  • 4
  • 33
  • 51
  • 3
    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. – user1934428 Jan 19 '16 at 10:27
  • 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) – Shimu Oct 08 '18 at 07:28
  • 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. – Kirill Sep 16 '20 at 16:39
  • Using `ruby 3.2.0 (2022-12-25 revision a528908271) [x86_64-linux]` I got the opposite result. `Hash: 6.5 / Struct: 10.8` – Rein Avila Apr 10 '23 at 07:31
16

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!"
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
Jon
  • 555
  • 5
  • 5
  • 2
    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. – sandre89 Apr 10 '17 at 12:56
12

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.

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
0

If you're just going to encapsulate the data, then a Hash (or an Array of Hashes) are fine. If you're planning to have the data manipulate or interact with other data, then a Struct can open some interesting possibilities:

Point = Struct.new(:x, :y)
point_a = Point.new(0,0)
point_b = Point.new(2,3)

class Point
  def distance_to another_point
    Math.sqrt((self.x - another_point.x)**2 + (self.y - another_point.y)**2)
  end
end

puts point_a.distance_to point_b
Mr. Demetrius Michael
  • 2,326
  • 5
  • 28
  • 40