Struct and Data
Prerequisites: Classes and objects
Many classes are just a few named values bundled together. Ruby has two shortcuts for building them without the boilerplate.
Struct — a mutable bundle of fields
Point = Struct.new(:x, :y) do
def distance_from_origin = Math.sqrt(x**2 + y**2)
end
pt = Point.new(3, 4)
p pt.x
p pt.distance_from_origin
pt.x = 6
p pt
p pt == Point.new(6, 4)
3
5.0
#<struct Point x=6, y=4>
true
Struct.new generates a class with a constructor, readers, writers,
== based on the values, and a readable inspect. The optional block
adds your own methods.
Data — an immutable value object (Ruby 3.2+)
Data is like Struct, but its values can't be changed after
creation. That makes it the better default for value objects —
things defined entirely by their values, such as money, coordinates, or
date ranges:
Money = Data.define(:amount, :currency) do
def to_s = format("%.2f %s", amount, currency)
def +(other)
raise ArgumentError, "currency mismatch" unless currency == other.currency
with(amount: amount + other.amount)
end
end
price = Money.new(amount: 10, currency: "EUR")
tax = Money.new(2.5, "EUR")
puts price + tax
p price
price.instance_variable_set(:@amount, 99) rescue puts "can't change a Data object"
12.50 EUR
#<data Money amount=10, currency="EUR">
can't change a Data object
Data.defineobjects can be created with keyword or positional arguments.- There are no setters.
with(...)returns a copy with some values changed. - Missing an argument raises an error, unlike
Struct, which quietly fills innil.
When to use which
- Data — small immutable values. Prefer it by default.
- Struct — when you genuinely need to change fields in place.
- A full class — when the object has real behavior, validation, or private state.
Common mistakes
- Using a plain hash (
{ x: 3, y: 4 }) for data passed all over a program. A typo in a hash key gives a silentnil; a typo in aDatafield raisesNoMethodErrorstraight away.