In general, what are the advantages and disadvantages of using an OpenStruct as compared to a Struct? What type of general use-cases would fit each of these?
-
The information regarding speed of Hash, Struct and OpenStruct is out of date. See http://stackoverflow.com/a/43987844/128421 for a more recent benchmark. – the Tin Man May 15 '17 at 22:04
-
2I have a few remarks about Struct vs. OpenStruct vs. Hash in my recent blog comment ["Structs inside out"](http://blog.rubybestpractices.com/posts/rklemme/017-Struct.html), just in case someone is interested. – Robert Klemme Oct 11 '09 at 11:51
9 Answers
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.
-
49"but they don't have the instance methods as would a class". well, there is a pretty common pattern to use it as a "normal class": `class Point < Struct.new(:x, :y); methods here; end` – tokland Dec 22 '11 at 11:39
-
12@tokland as for today, the "preferred" approach of customizing the struct with methods is to pass the block to constructor `Point = Struct.new(:x, :y) { methods here }`. ([source](http://ruby-doc.org/core-2.3.0/Struct.html#method-c-new)) Of course, `{ ... }` there can be written as a multi-line block (`do ... end`) and, I think, that's the preferred way. – Ivan Kolmychek Mar 09 '16 at 13:11
-
@tokland good. I just wanted to clarify that now there is a nicer approach, seeing as your comment is highly up voted, so, people new to ruby can actually think "OK, so that's how it should be done, 'cause everyone agree with that, right?" :) – Ivan Kolmychek Mar 09 '16 at 19:09
-
7A question: once you arrive at the moment you want to add methods to your struct, why not use a class? – jaydel Oct 13 '17 at 14:44
Other benchmark:
require 'benchmark'
require 'ostruct'
REP = 100000
User = Struct.new(:name, :age)
USER = "User".freeze
AGE = 21
HASH = {:name => USER, :age => AGE}.freeze
Benchmark.bm 20 do |x|
x.report 'OpenStruct slow' do
REP.times do |index|
OpenStruct.new(:name => "User", :age => 21)
end
end
x.report 'OpenStruct fast' do
REP.times do |index|
OpenStruct.new(HASH)
end
end
x.report 'Struct slow' do
REP.times do |index|
User.new("User", 21)
end
end
x.report 'Struct fast' do
REP.times do |index|
User.new(USER, AGE)
end
end
end
For the impatient who wants to get an idea of the benchmark results, without running them themselves, here is the output of the code above (on an MB Pro 2.4GHz i7)
user system total real
OpenStruct slow 4.430000 0.250000 4.680000 ( 4.683851)
OpenStruct fast 4.380000 0.270000 4.650000 ( 4.649809)
Struct slow 0.090000 0.000000 0.090000 ( 0.094136)
Struct fast 0.080000 0.000000 0.080000 ( 0.078940)

- 1
- 1

- 2,149
- 18
- 23
-
5with ruby 2.14 the difference is smaller 0.94-0.97 with OpenStruct vs 0.02-0.03 with Ostruct (MB Pro 2.2Ghz i7) – basex Mar 23 '15 at 17:06
-
1OpenStruct is equivalent in speed to using Struct. See http://stackoverflow.com/a/43987844/128421. – the Tin Man May 15 '17 at 20:05
UPDATE:
Timings for creating 1 million instances:
0.357788 seconds elapsed for Class.new (Ruby 2.5.5)
0.764953 seconds elapsed for Struct (Ruby 2.5.5)
0.842782 seconds elapsed for Hash (Ruby 2.5.5)
2.211959 seconds elapsed for OpenStruct (Ruby 2.5.5)
0.213175 seconds elapsed for Class.new (Ruby 2.6.3)
0.335341 seconds elapsed for Struct (Ruby 2.6.3)
0.836996 seconds elapsed for Hash (Ruby 2.6.3)
2.070901 seconds elapsed for OpenStruct (Ruby 2.6.3)
0.936016 seconds elapsed for Class.new (Ruby 2.7.2)
0.453067 seconds elapsed for Struct (Ruby 2.7.2)
1.016676 seconds elapsed for Hash (Ruby 2.7.2)
1.482318 seconds elapsed for OpenStruct (Ruby 2.7.2)
0.421272 seconds elapsed for Class.new (Ruby 3.0.0)
0.322617 seconds elapsed for Struct (Ruby 3.0.0)
0.719928 seconds elapsed for Hash (Ruby 3.0.0)
35.130777 seconds elapsed for OpenStruct (Ruby 3.0.0) (oops!)
0.443975 seconds elapsed for Class.new (Ruby 3.0.1)
0.348031 seconds elapsed for Struct (Ruby 3.0.1)
0.737662 seconds elapsed for Hash (Ruby 3.0.1)
16.264204 seconds elapsed for SmartHash (Ruby 3.0.1) (meh)
53.396924 seconds elapsed for OpenStruct (Ruby 3.0.1) (oops!)
0.407767 seconds elapsed for Class.new (Ruby 3.0.3)
0.326846 seconds elapsed for Struct (Ruby 3.0.3)
0.652807 seconds elapsed for Hash (Ruby 3.0.3)
10.679195 seconds elapsed for SmartHash (Ruby 3.0.3)
35.212618 seconds elapsed for OpenStruct (Ruby 3.0.3)
See: Ruby 3.0.0 Bug #18032 was closed, because it is a feature, not a bug
Quotes:
OpenStruct is now considered as "an antipattern", so I recommend you no longer to use OpenStruct.
[the Ruby team] prioritized correctness over performance and came back to a solution similar to that of Ruby 2.2
Old Answers:
As of Ruby 2.4.1 OpenStruct and Struct are much closer in speed. See https://stackoverflow.com/a/43987844/128421
For completeness: Struct vs. Class vs. Hash vs. OpenStruct
Running similar code as burtlo's, on Ruby 1.9.2, (1 of 4 cores x86_64, 8GB RAM) [table edited to align columns]:
creating 1 Mio Structs : 1.43 sec , 219 MB / 90MB (virt/res) creating 1 Mio Class instances : 1.43 sec , 219 MB / 90MB (virt/res) creating 1 Mio Hashes : 4.46 sec , 493 MB / 364MB (virt/res) creating 1 Mio OpenStructs : 415.13 sec , 2464 MB / 2.3GB (virt/res) # ~100x slower than Hashes creating 100K OpenStructs : 10.96 sec , 369 MB / 242MB (virt/res)
OpenStructs are sloooooow and memory intensive , and don't scale well for large data sets
Here's the script to reproduce the results:
require 'ostruct'
require 'smart_hash'
MAX = 1_000_000
class C;
attr_accessor :name, :age;
def initialize(name, age)
self.name = name
self.age = age
end
end
start = Time.now
collection = (1..MAX).collect do |i|
C.new('User', 21)
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for Class.new (Ruby #{RUBY_VERSION})"
s = Struct.new(:name, :age)
start = Time.now
collection = (1..MAX).collect do |i|
s.new('User', 21)
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for Struct (Ruby #{RUBY_VERSION})"
start = Time.now
collection = (1..MAX).collect do |i|
{:name => "User" , :age => 21}
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for Hash (Ruby #{RUBY_VERSION})"
start = Time.now
collection = (1..MAX).collect do |i|
s = SmartHash[].merge( {:name => "User" , :age => 21} )
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for SmartHash (Ruby #{RUBY_VERSION})"
start = Time.now
collection = (1..MAX).collect do |i|
OpenStruct.new(:name => "User" , :age => 21)
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for OpenStruct (Ruby #{RUBY_VERSION})"

- 33,354
- 5
- 79
- 106
-
-
1Hi @Tilo, could you share your code to get the results above? I want to use it to compare Struct & OStruct with Hashie::Mash. Thanks. – Donny Kurnia Mar 30 '14 at 11:01
-
1Hey @Donny, I just saw the upvote and realized that this was measured in 2011 - I need to re-run this with Ruby 2.1 :P not sure if I have that code, but it should be simple to reproduce. I'll try to fix it soon. – Tilo Feb 15 '15 at 18:46
-
2As of Ruby 2.4.1 OpenStruct and Struct are much closer in speed. See http://stackoverflow.com/a/43987844/128421 – the Tin Man May 15 '17 at 20:05
The use cases for the two are quite different.
You can think of the Struct class in Ruby 1.9 as an equivalent to the struct
declaration in C. In Ruby Struct.new
takes a set of field names as arguments and returns a new Class. Similarly, in C, a struct
declaration takes a set of fields and allows the programmer to use the new complex type just like he would any built-in type.
Ruby:
Newtype = Struct.new(:data1, :data2)
n = Newtype.new
C:
typedef struct {
int data1;
char data2;
} newtype;
newtype n;
The OpenStruct class can be compared to an anonymous struct declaration in C. It allows the programmer to create an instance of a complex type.
Ruby:
o = OpenStruct.new(data1: 0, data2: 0)
o.data1 = 1
o.data2 = 2
C:
struct {
int data1;
char data2;
} o;
o.data1 = 1;
o.data2 = 2;
Here are some common use cases.
OpenStructs can be used to easily convert hashes to one-off objects which respond to all the hash keys.
h = { a: 1, b: 2 }
o = OpenStruct.new(h)
o.a = 1
o.b = 2
Structs can be useful for shorthand class definitions.
class MyClass < Struct.new(:a,:b,:c)
end
m = MyClass.new
m.a = 1

- 158,662
- 42
- 215
- 303

- 557
- 4
- 7
OpenStructs use significantly more memory and are slower performers versus Structs.
require 'ostruct'
collection = (1..100000).collect do |index|
OpenStruct.new(:name => "User", :age => 21)
end
On my system the following code executed in 14 seconds and consumed 1.5 GB of memory. Your mileage might vary:
User = Struct.new(:name, :age)
collection = (1..100000).collect do |index|
User.new("User",21)
end
That finished nearly instantaneously and consumed 26.6 MB of memory.

- 158,662
- 42
- 215
- 303

- 1,216
- 1
- 12
- 12
-
3But you are aware that the OpenStruct test creates a lot temporary hashes. I suggest a slightly modified benchmark - which still supports your verdict (see below). – Robert Klemme Dec 16 '10 at 09:19
Struct
:
>> s = Struct.new(:a, :b).new(1, 2)
#=> #<struct a=1, b=2>
>> s.a
#=> 1
>> s.b
#=> 2
>> s.c
#=> NoMethodError: undefined method `c` for #<struct a=1, b=2>
OpenStruct
:
>> require 'ostruct'
#=> true
>> os = OpenStruct.new(a: 1, b: 2)
#=> #<OpenStruct a=1, b=2>
>> os.a
#=> 1
>> os.b
#=> 2
>> os.c
#=> nil

- 45,245
- 23
- 243
- 245

- 22,759
- 8
- 120
- 116
Have a look at the API with regard to the new method. A lot of the differences can be found there.
Personally, I quite like OpenStruct, as I don't have to define the structure of the object beforehand, and just add stuff as I want. I guess that would be its main (dis)advantage?

- 158,662
- 42
- 215
- 303

- 8,963
- 3
- 33
- 35
Using @Robert code, I add Hashie::Mash to the benchmark item and got this result:
user system total real
Hashie::Mash slow 3.600000 0.000000 3.600000 ( 3.755142)
Hashie::Mash fast 3.000000 0.000000 3.000000 ( 3.318067)
OpenStruct slow 11.200000 0.010000 11.210000 ( 12.095004)
OpenStruct fast 10.900000 0.000000 10.900000 ( 12.669553)
Struct slow 0.370000 0.000000 0.370000 ( 0.470550)
Struct fast 0.140000 0.000000 0.140000 ( 0.145161)

- 5,260
- 5
- 35
- 52
-
Your benchmark is really strange. I got the following result with ruby2.1.1 on a i5 mac : https://gist.github.com/nicolas-besnard/b1c218d103ea7b572a54#file-result-md – cappie013 Dec 17 '14 at 08:23
-
Well, the result will vary between ruby version used, and hardware used to run it. But the pattern is still the same, OpenStruct is the slowest, Struct is the fastest. Hashie fall in the middle. – Donny Kurnia Dec 29 '14 at 23:56
Not actually an answer to the question, but a very important consideration if you care about performance. Please notice that every time you create an OpenStruct
the operation clears the method cache, which means your application will perform slower. The slowness or not of OpenStruct
is not just about how it works by itself, but the implications that using them bring to the whole application: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md#openstructs

- 1,339
- 15
- 27