I have seen codebases using Structs to wrap around attributes and behavior inside a class. What is the difference between a Ruby Class and a Struct? And when should one be used over the other.?
7 Answers
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).
4 Comments
Struct is a class that generates classes. It is just there to save you typing.To add to the other answers, there are some things you can not do with a Struct, and some than you can.
For example, you can not create a Struct with no arguments:
Bar = Struct.new
=> ArgumentError: wrong number of arguments (given 0, expected 1+)
Bar = Struct.new(:bar)
bar = Bar.new(nil)
bar.class
=> Bar
However, a class will let you do that:
class Foo; end
foo = Foo.new
foo.class
=> Foo
You can not set a default value for Struct arguments:
Bar = Struct.new(bar: 'default')
=> ArgumentError: unknown keyword: bar
Bar = Struct.new(bar = 'default')
=> NameError: identifier default needs to be constant
But you can do it with a class, either passing a hash, were the arguments can be in any order or even missing:
class Bar
attr_reader :bar, :rab
def initialize(bar: 'default', rab:)
@bar = bar
@rab = rab
end
end
bar = Bar.new(rab: 'mandatory')
bar.rab
=> 'mandatory'
bar.bar
=> 'default'
bar = Bar.new(rab: 'mandatory', bar: 'custom_value')
bar.rab
=> 'mandatory'
bar.bar
=> 'custom_value'
or passing the values directly, were the arguments should be given in the same order, with the defaulted ones always at the end:
class Bar
attr_reader :rab, :bar
def initialize(rab, bar = 'default')
@rab = rab
@bar = bar
end
end
bar = Bar.new('mandatory')
bar.rab
=> 'mandatory'
bar.bar
=> 'default'
bar = Bar.new('mandatory', 'custom_value')
bar.rab
=> 'mandatory'
bar.bar
=> 'custom_value'
You can not do any of that with Structs, unless you set default values for your arguments in this super verbose way:
A = Struct.new(:a, :b, :c) do
def initialize(a:, b: 2, c: 3)
super(a, b, c)
end
end
(example taken from this answer)
You can define methods in a Struct:
Foo = Struct.new(:foo) do
def method(argument)
# do something with argument
end
end
end
Structs can be useful to create data objects, like the point example mentioned in one of the answers.
I sometimes use them to create fakes and mocks in tests in a simple way. Sometimes RSpec allow(foo).to receive(:blah) etc. can get a bit too verbose and using a Struct is much simple.
1 Comment
Struct is a Ruby shorthand for creating Classes. Using Struct where applicable simplifies your code. There is a good discussion of this at https://www.rubytapas.com/2012/11/07/episode-020-struct/
4 Comments
Some of the qualities of using the tool Struct instead of creating a ruby class are the following:
A ruby class would take 8 lines to be defined with all the necessary attributes accessors and constructor method and the Struct only take you one line of code
After assigning a Struct to a variable I can get and set values using Hash-like subscript syntax and inside it, I can use symbols or strings interchangeably as keys
Struct have a equality operator. Struct defines it so that instances with equal attributes are considered equal
Unlike attributes defined with attr_accessor Structs can introspect and iterate their attributes, using methods such as members (that will return the instance attributes), each or each_pair (in which you can iterate over the name of the attribute and it's value)
Structs also include enumerables so we have a full complement of enumerable methods as well
Source: Graceful dev website
In terms of system performance, this article affirms that classes are faster than structs
Comments
I'd like to to @sam_forgot suggested benchmark. The comparison is not very fair. Both class and struct, these days, support keyword arguments. Using keyword arguments on each has opposite effects, as you can see from my example struct's with keyword arguments performance is not that dramatically different from the class.
require 'benchmark'
REP=1000000
SUser = Struct.new(:name, :age)
SUserK = Struct.new(:name, :age, keyword_init: true)
DATA = { name: "Harry", age: 75 }
DATA2 = DATA.values
class CUser
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
class CUserK
attr_accessor :name, :age
def initialize(name:, age:)
@name = name
@age = age
end
end
Benchmark.bmbm do |x|
x.report 'Struct create and access, without keyword arguments' do
REP.times do
user = SUser.new(DATA)
"#{user.name} - #{user.age}"
end
end
x.report 'Struct create and access, with keyword arguments' do
REP.times do
user = SUserK.new(**DATA)
"#{user.name} - #{user.age}"
end
end
x.report 'Class create and access, without keyword arguments' do
REP.times do
user = CUser.new(*DATA2)
"#{user.name} - #{user.age}"
end
end
x.report 'Class create and access, with keyword arguments' do
REP.times do
user = CUserK.new(DATA)
"#{user.name} - #{user.age}"
end
end
end
Rehearsal ---------------------------------------------------------------------------------------
Struct create and access, without keyword arguments 3.484609 0.011974 3.496583 ( 3.564523)
Struct create and access, with keyword arguments 0.965959 0.005543 0.971502 ( 1.007738)
Class create and access, without keyword arguments 0.624603 0.003999 0.628602 ( 0.660931)
Class create and access, with keyword arguments 0.901494 0.004926 0.906420 ( 0.952149)
------------------------------------------------------------------------------ total: 6.003107sec
user system total real
Struct create and access, without keyword arguments 3.300488 0.010372 3.310860 ( 3.339511)
Struct create and access, with keyword arguments 0.876742 0.004354 0.881096 ( 0.903551)
Class create and access, without keyword arguments 0.553393 0.003962 0.557355 ( 0.568985)
Class create and access, with keyword arguments 0.831672 0.004811 0.836483 ( 0.850224)
1 Comment
SUser.new(*DATA2) is about 5 times faster then SUser.new(DATA) and is about the same speed as using class without keyword arguments.There is quite a big practical performance difference, example behavior in ruby 2.6.3p62:
user system total real
Struct create and access 3.052825 0.005204 3.058029 (3.066316)
Class create and access 0.738605 0.001467 0.740072 (0.743738)
Example code:
require 'benchmark'
REP=1000000
SUser = Struct.new(:name, :age)
DATA = { name: "Harry", age: 75 }
class User
attr_accessor :name, :age
def initialize(name:, age:)
@name = name
@age = age
end
end
Benchmark.bm 20 do |x|
x.report 'Struct create and access' do
REP.times do
user = SUser.new(DATA)
"#{user.name} - #{user.age}"
end
end
x.report 'Class create and access' do
REP.times do
user = User.new(DATA)
"#{user.name} - #{user.age}"
end
end
end
Comments
Think of Struct as a more high-level thing than a Class.
Depending on your goal whether you aim for internal simplicity or the practical power, you chose between Class that has no much magic and Struct that has all those additional fancy methods to work with items.
And only if you are still not sure, then look at the points others have mentioned: having attributes inaccessible by default, inheritance, expandability the attributes list.