34

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.?

John Topley
  • 113,588
  • 46
  • 195
  • 237
rshetty
  • 491
  • 1
  • 5
  • 10

6 Answers6

31

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:

Community
  • 1
  • 1
lcguida
  • 3,787
  • 2
  • 33
  • 56
  • 1
    Thats true, When both do the same thing. Why we have both Class and a Struct. My argument is When to use a class and when to use a Struct in an application. What are the use cases wherein they fit ? – rshetty Sep 16 '14 at 16:53
  • For the point example, you could also look in the ostruct (mentioned on the link). But the basic idea is not need to create a class in order to have grouped accessible attributes – lcguida Sep 16 '14 at 17:17
  • 3
    "Why we have both Class and a Struct" – I don't understand what you mean. `Struct` is a class that generates classes. It is just there to save you typing. – Jörg W Mittag Sep 16 '14 at 20:55
  • 3
    To me it sounds qiute strange: "we use Structs when we need a group of accessible attributes without having to write an explicit class." Which leads to having to write an explicit Struct. Explicit Struct vs explicit Class? – zuba Feb 15 '16 at 14:54
9

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.

mestevens
  • 359
  • 4
  • 11
5

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/

KAGsundaram
  • 59
  • 1
  • 2
  • Disclaimer: I am the editor at RubyTapas; I found this Stack Overflow question while researching links to place in that article. – KAGsundaram Mar 27 '17 at 17:42
  • 3
    Welcome to Stack Overflow! Whilst this may theoretically answer the question, [it would be preferable](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – MarceloBarbosa Mar 27 '17 at 17:49
  • 1
    Extra disclaimer: I'm the Head Chef at RubyTapas, and this answer was posted before I had communicated (or, indeed, formulated) a policy on posting answers that link to RubyTapas. I don't think either of us have the ability to directly remove it, but we're OK with its removal if it goes against SO community standards. – Avdi Mar 27 '17 at 17:52
  • @Avdi It doesn't need to be removed, just amended to include salient points. Any poster should be able to delete their own posts. – Dave Newton Aug 31 '17 at 15:14
1

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)
Matas
  • 49
  • 1
  • 6
  • On my PC `SUser.new(*DATA2)` is about 5 times faster then `SUser.new(DATA)` and is about the same speed as using class without keyword arguments. – complistic Jul 17 '22 at 09:20
0

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
sam_forgot
  • 46
  • 2
0

Some of the qualities of using the tool Struct instead of creating a ruby class are the following:

  1. 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

  2. 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

  3. Struct have a equality operator. Struct defines it so that instances with equal attributes are considered equal

  4. 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)

  5. 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