38

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 7

32

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:

Sign up to request clarification or add additional context in comments.

4 Comments

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

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

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/

4 Comments

Disclaimer: I am the editor at RubyTapas; I found this Stack Overflow question while researching links to place in that article.
Welcome to Stack Overflow! Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.
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 It doesn't need to be removed, just amended to include salient points. Any poster should be able to delete their own posts.
3

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

Comments

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)

1 Comment

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

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.