Struct vs Data in Ruby 3.2

In the world of Ruby programming, the Struct and Data classes are both used to create simple data objects. This article takes an in-depth approach to understanding the similarities, differences, and optimal usage strategies of these classes in Ruby.

Understanding Struct and Data Classes

Before we delve deeper, let's understand the basic concept of these classes.
Struct is a built-in Ruby class that allows you to define simple data structures. It's mutable, meaning that you can change the values after initializing it.
Data, on the other hand, arrived with Ruby 3.2 and is essentially a Struct but without writer methods. This means that once it's created, it's immutable. You can't change the values because it doesn't have a setter method. This class must be used with Data.define. Let's see it in action.

Using Data

# Data is basically a struct with no writer methods
# basic usage of the default Data.define from Ruby 3.2
Project = Data.define(:title, :description)
project = Project.new("Example", "Example description")
puts project.title
# => "Example"
project.title = 'abcd' # it will raise error because it's immutable

In the code snippet above, we define a data class Project with two attributes, title and description. We then instantiate it and print the title, which works fine. But when we try to change the title, it throws an error because it is immutable.

Extending the Data class

We can create a new class that extends Data.define and add methods to it as required.

class Project < Data.define(:title, :description)
  def cover
    "#{title} #{description}"
  end
end

# we can use keyword arguments
project = Project.new(description: "Example description", title: "Title")
puts project.cover
# => "Title Example description"

In the new class Project, we have added a cover method that outputs the title and description.

Understanding Struct Class

Like we mentioned before, a Struct is mutable. Let's see a demonstration.

# data vs struct - the main difference is that the struct is mutable, so we can change values after initialization
ProjectStruct = Struct.new(:title, :description)
project = ProjectStruct.new("Example", "Example description")
puts project.title
# => "Example"
project.title = 'aa'
puts project.title
# => "aa"

Here we initialized ProjectStruct with Example, but we could later change it to aa.

Benchmarking Data vs. Struct vs OpenStruct

From the performance analysis and benchmarking between the Data, Struct and OpenStruct classes, it appears that Data and Struct are quite similar in terms of performance, with OpenStruct lagging behind as it is considerably slower(based on https://github.com/lucianghinda/value-object-in-ruby-benchmarks). In my opinion, OpenStruct should be deprecated.

Summary

The introduction of the Data class to the Ruby language brought with it the ability to define immutable data structures, very similar to structs, but without the ability to mutate data once it's set. When deciding whether to use Struct or Data, it really comes down to whether you need your objects to be mutable or immutable.
understand

Happy coding!