Intro

With Ruby, Struct and OpenStruct are great tools to have at your disposal for structuring data. It’s unfortunate that the names are so similar, but let’s tear these two apart. First, we’ll dig into each and see what uses cases they excel at. Then, we’ll compare and summarize when you should use one over the other.

Struct

We’ll start with Struct. Ruby’s official documentation bills Struct as:

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

Struct is essentially a shortcut for building simple classes with attributes and maybe a method or two. Let’s see how it’s used:

User = Struct.new(:first, :last) do
  def email
    "#{first}.#{last}@mrod.space"
  end
end

mike = User.new("Michael", "Rodrigues")

mike.first
#=> "Michael"

mike.last
#=> "Rodrigues"

mike.email
#=> "Michael.Rodrigues@mrod.space"

mike.class
#=> User

If the example looks familiar, I basically ripped it right out of the Ruby core documentation. Make a mental note of that; Struct is part of Ruby core. Its source code is written in C. This is in contrast to OpenStruct which is part of the standard library. It’s written in Ruby and you have to require it before you use it. You don’t need to do anything to use Struct.

Now, back to Struct. It might just be easier to show you a class that gives you what Struct gives you with much less work:

class User

  attr_accessor :first
  attr_accessor :last

  def initialize(first, last)
    @first = first
    @last = last
  end

  def email
    "#{first}.#{last}@mrod.space"
  end

end

mike.first
#=> "Michael"

mike.last
#=> "Rodrigues"

mike.email
#=> "Michael.Rodrigues@mrod.space"

mike.class
#=> User

Instantiated objects of our User class above behave exactly like instantiations of our User Struct in the example before it. Remember, Struct.new returns a class, keep this in mind as we transition our focus to OpenStruct.

OpenStruct

The official Ruby documentation describes OpenStruct as:

a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s metaprogramming to define methods on the class itself.

Unlike Struct, OpenStruct isn’t going to define a new class to instantiate objects from. However, it will let you arbitrarily define new attributes on the fly. Let’s look a tweaked example from the Ruby documentation:

require "ostruct" # remember, part of std lib, not core

event = OpenStruct.new
event.timestamp = Time.now
event.message = "Something notable happened."

event.timestamp
#=> 2019-08-29 23:20:29 -0700

event.message
#=> "Something Notable happened."

event.sender
#=> nil

event.sender = "Michael"
event.sender
#=> "Michael"

We instantiate our OpenStruct object, event, and instantly set two attributes on it. Notice we didn’t define them beforehand, we just set them at will. We can easily get them by name.

You can also pass in a Hash at instantiation:

require "ostruct"

event = OpenStruct.new(
			{ 
			  timestamp: Time.now,
			  message: "Short message.",
			  sender: "Michael"
		        }
)

event.sender
#=> "Michael"

Summary

So, in summary:

  • Struct.new returns a class with fixed attributes
  • OpenStruct.new returns an instance of OpenStruct, attributes can be added and removed after instantiation

  • Struct is part of Ruby core, written in C
  • OpenStruct is part of the Ruby Standard Library, written in Ruby

  • Struct is instantiated with a class name, list of attributes, and optional methods
  • OpenStruct can be instantiated with a Hash

When to use Struct vs. OpenStruct

Use Struct when speed is a concern, and you know all of your attributes at instantiation. With Struct, you trade flexibility for performance.

Use OpenStruct if you’re OK to sacrifice some speed for the flexibility of defining attributes after instantiation. You can use a Hash but you won’t get the “syntactical sugar” that OpenStruct gives you with the dynamically created setter and getter methods.