Struct in Ruby
In this article, we’re going to explore the following topics:
- the
Struct
class - structure types and structures
- structure type definition behind the scene
The Struct class
A structure is a dummy data container. Unlike an object, it’s used for bundling and serving a set of information without any logic.
It provides a pair of getter/setter methods for each attribute that it contains. This is similar to the attr_accessor
method for classes.
Feel free to read the
Attributes in Ruby
article if you are unfamiliar with theattr_*
methods in Ruby.
The Struct
class is a structure-type builder. This class is in charge of defining new structure types that can generate structures afterward.
Let’s have a look at its ancestor chain
irb> Struct.class
=> Class
irb> Struct.ancestors
=> [Struct, Enumerable, Object, Kernel, BasicObject]
The Struct
class inherits from the default Object
class.
It also includes the Enumerable
module which is in charge of adding a bunch of searching, sorting, and traversal methods to a class.
This class shares the exact same ancestor chain as the Array
and Hash
classes.
Feel free to read the
Ruby Object Model
article if you are unfamiliar with theObject
class and the ancestor chain.Feel free to read the
The Enumerable module in Ruby: Part I
article if you are unfamiliar with theEnumerable
module.
Structure types and structures
A structure type is a blueprint (class) that contains an immutable list of attributes — also called members.
A structure is the in-memory representation of this blueprint (object).
Now let’s see how to create a structure type.
The Struct::new
method is a structure-type builder. It allows you to define a new structure type associated with a bunch of defined members passed as parameters
In the first line, we define the Address
structure type which contains the street
, city
and zip
members.
Then we instantiate a structure that is of Address
type that we store in the home
variable.
Each argument of Address.new(‘Broadway’, ‘NYC’, 10002)
matches the corresponding argument of the Struct.new(:street, :city, :zip)
in the given order.
Here, we can see that there are 3 ways to access the value of a member:
home.street
: thestreet
accessor methodhome[:city]
: theStruct#[]
with asymbol
keyhome['zip']
: theStruct#[]
with astring
key
Also, notice that if you try to access a non-existing member then a NoMethodError
or a NameError
is raised depending on the way to access this member.
It’s also possible to modify the value of a member for a given structure
Here, we can see that there are 3 ways to modify the value of a member:
home.street=
: thestreet=
accessor methodhome[:city]
: theStruct#[]=
with asymbol
keyhome['zip']
: theStruct#[]=
with astring
key
Also, notice that if you try to modify a non-existing member then a NoMethodError
or a NameError
is raised depending on the way to modify this member.
Now that we are more familiar with structures and structure types, let’s dig into how Structure types are defined behind the scene.
Structure type definition behind the scene
Like any class in Ruby, the Struct::new
method should instantiate an object of type Struct
.
But what if I tell you that the Struct
class is not instantiable?
irb> Struct.allocate
TypeError (allocator undefined for Struct)
irb> Struct.methods(false)
=> [:new]
In effect, the Struct#allocate
method — in charge of allocating the memory space needed to contain a Struct
object — is undefined at the Struct
class definition.
So, the Struct
class cannot allocate the needed memory to instantiate an object of type Struct
.
Also, the Struct
class overrides the BasicObject#new
method by implementing its own version.
So, how structure types are defined if we cannot instantiate a Struct
?
Behind the scene, Ruby makes a little bit of magic to give the illusion that this class is instantiable.
And all this magic is defined in the Struct#new
method.
If effect, this method doesn’t instantiate a Struct
but, instead, creates a subclass of its own
Here, the Address
constant is actually a Class
that inherits from the Struct
class.
This allows the Address
class to have access to all of the methods and internals of the Struct
class.
So, a structure type is in reality a named class that inherits from the Struct
class and a structure is simply an instance of this named class.
This powerful design allows our structure type to enjoy all the mechanisms that a class provides in Ruby such as class opening, inheritance, mixins, etc...
For example, we can re-open the Address
class to add a full_address
method
This is the basic use of the Struct::new
method.
Otherwise, there is another way to use this method
By providing the structure type name as the first argument of the Struct::new
, the method automatically defines a new class under the scope of the Struct
class which also inherits from Struct
.
Then we can instantiate the freshly defined Struct::Address
structure type and store the structure in the home
variable.
Ruby Mastery
We’re currently finalizing our first online course: Ruby Mastery.
Join the list for an exclusive release alert! 🔔
Also, you can follow us on x.com as we’re very active on this platform. Indeed, we post elaborate code examples every day.
💚