When Is It Better to Use a Struct Rather Than a Hash in Ruby

When to use Struct instead of Hash in Ruby?

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.

When is it better to use a Struct rather than a Hash in Ruby?

Personally I use a struct in cases when I want to make a piece of data act like a collection of data instead of loosely coupled under a Hash.

For instance I've made a script that downloads videos from Youtube and in there I've a struct to represent a Video and to test whether all data is in place:


Video = Struct.new(:title, :video_id, :id) do
def to_s
"http://youtube.com/get_video.php?t=#{id}&video_id=#{video_id}&fmt=18"
end

def empty?
@title.nil? and @video_id.nil? and @id.nil?
end
end

Later on in my code I've a loop that goes through all rows in the videos source HTML-page until empty? doesn't return true.

Another example I've seen is James Edward Gray IIs configuration class which uses OpenStruct to easily add configuration variables loaded from an external file:

#!/usr/bin/env ruby -wKU

require "ostruct"

module Config
module_function

def load_config_file(path)
eval <<-END_CONFIG
config = OpenStruct.new
#{File.read(path)}
config
END_CONFIG
end
end

# configuration_file.rb
config.db = File.join(ENV['HOME'], '.cool-program.db')
config.user = ENV['USER']

# Usage:
Config = Config.load_config('configuration_file.rb')
Config.db # => /home/ba/.cool-program.db
Config.user # => ba
Config.non_existant # => Nil

The difference between Struct and OpenStruct is that Struct only responds to the attributes that you've set, OpenStruct responds to any attribute set - but those with no value set will return Nil

When should I use an OpenStruct instead of a Hash?

I think this mostly comes down to a performance decision. From the Ruby Documentation:

An OpenStruct utilizes Ruby’s method lookup structure to and find and define the necessary methods for properties. This is accomplished through the method method_missing and define_method.

This should be a consideration if there is a concern about the performance of the objects that are created, as there is much more overhead in the setting of these properties compared to using a Hash or a Struct.

Additionally, something like a Hash has additional functionality with all of the methods it provides (has_key?, include?, etc.). The OpenStruct is a very simple object from that standpoint, but if you don't have any concerns from a performance standpoint and just want an easy object to work with, OpenStruct is a good choice.

When should I use Struct vs. OpenStruct?

With an OpenStruct, you can arbitrarily create attributes. A Struct, on the other hand, must have its attributes defined when you create it. The choice of one over the other should be based primarily on whether you need to be able to add attributes later.

The way to think about them is as the middle ground of the spectrum between Hashes on one side and classes on the other. They imply a more concrete relationship amongst the data than does a Hash, but they don't have the instance methods as would a class. A bunch of options for a function, for example, make sense in a hash; they're only loosely related. A name, email, and phone number needed by a function could be packaged together in a Struct or OpenStruct. If that name, email, and phone number needed methods to provide the name in both "First Last" and "Last, First" formats, then you should create a class to handle it.

Ruby Class vs Struct

From the Struct docs:

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

The Struct class generates new subclasses that hold a set of members and their values. For each member a reader and writer method is created similar to Module#attr_accessor.

So, if I want a Person class that I can access a name attribute (read and write), I either do it by declaring a class:

class Person
attr_accessor :name

def initalize(name)
@name = name
end
end

or using Struct:

Person = Struct.new(:name)

In both cases I can run the following code:

 person = Person.new
person.name = "Name"
#or Person.new("Name")
puts person.name

When use it?

As the description states we use Structs when we need a group of accessible attributes without having to write an explicit class.

For example I want a point variable to hold X and Y values:

point = Struct.new(:x, :y).new(20,30)
point.x #=> 20

Some more examples:

  • http://blog.steveklabnik.com/posts/2012-09-01-random-ruby-tricks--struct-new
  • "When to use Struct instead of Hash in Ruby?" also has some very good points (comparing to the use of hash).

Whats are some important differences between an OpenStruct and a Hash?

OpenStructs are sloooooooooow and memory intensive , and don't scale well for large data sets.
Creating 1 million OpenStructs is ~100x slower than creating 1 million Hashes.

This has been discussed in detail here:

When should I use Struct vs. OpenStruct?

returning struct data as a hash in ruby

Ruby <= 1.9.3 has OpenStruct#marshall_dump and Struct#each_pair:

Person = Struct.new(:name, :age)
person = Person.new("Jamie", 23)
person_hash = Hash[person.each_pair.to_a]
#=> {:age=>23, :name=>"Jamie"}

Ruby 2.0 provides Struct#to_h and OpenStruct#to_h:

Person = Struct.new(:name, :age)
person = Person.new("Jamie", 23)
person_hash = person.to_h
#=> {:age=>23, :name=>"Jamie"}

Ruby: Struct vs Initialize

The class (non-struct) has a simpler ancestry tree:

>> Fruit.ancestors
=> [Fruit, Object, Kernel, BasicObject]

As compared to the struct version:

>> Fruit.ancestors
=> [Fruit, #<Class:0x1101c9038>, Struct, Enumerable, Object, Kernel, BasicObject]

So, the Struct class could be mistaken for an array (rare, but absolutely could happen)

fruit = Fruit.new("yo")
# .. later
fruit.each do |k|
puts k
end
# outputs: yo

So... I use Structs as throw-away data objects. I use "real" classes in my domain and application.

When should I use a hash vs a class

From an OO point of view (and you're programming in Java...), you should use classes for that.

I don't think it adds any complexity at all. That's what classes are made for. If those classes don't have any methods but getters and setters, well, that's the place for so called TO's (Transfer Objects). And even while this should not be considered as an design argument, of course it's more debug-friendly than hashes.



Related Topics



Leave a reply



Submit