Struct & OpenStruct

A Struct is a built-in Ruby shortcut that generates a small class bundling a fixed set of named attributes — complete with constructor, accessors, and value equality — in a single line of code.

Learn Struct & OpenStruct in our free Ruby course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You'll also meet the flexible runtime-attribute OpenStruct and the immutable Data.define (Ruby 3.2+), and learn when each beats a full class.

What You'll Learn in This Lesson

1️⃣ Defining a Struct

Struct.new(:a, :b) returns a new class with readers and writers for each attribute and a constructor. Pass a block to add your own methods — inside them, you can refer to the attributes by name directly.

2️⃣ keyword_init, Equality & Conversions

Add keyword_init: true to construct by name instead of position. Structs come with value-based == for free, plus handy conversions like to_a , to_h , and the class method members .

3️⃣ OpenStruct & Data.define

After require 'ostruct' , an OpenStruct lets you add attributes at runtime and returns nil for unknown ones. For an immutable value object, Data.define (Ruby 3.2+) gives readers and value equality but freezes instances — no writers.

Your turn. Fill in each ___ blank, then run it.

Model cart items as a Struct with a subtotal method, then sum them with sum(&:subtotal) . Run with ruby cart.rb .

📋 Quick Reference — Struct & Friends

Practice quiz

What does Struct.new(:x, :y) return?

  • An instance with x and y
  • A hash
  • A new class with readers, writers, and a constructor
  • An array

Answer: A new class with readers, writers, and a constructor. Struct.new returns a new class that bundles the named attributes with accessors and a constructor.

By convention, you assign the result of Struct.new to a...

  • constant (capitalized name)
  • lowercase local variable
  • global variable
  • symbol

Answer: constant (capitalized name). Assign it to a constant like Point so it becomes a proper, named class.

What does keyword_init: true change about a Struct?

  • Makes it immutable
  • Removes the writers
  • Adds inheritance
  • Lets you construct with named arguments

Answer: Lets you construct with named arguments. With keyword_init: true you pass named arguments like User.new(name: "Ada", role: "admin").

Two Struct instances with the same members compare with == as...

  • always false
  • equal (true) when same struct class and members match
  • an error
  • equal only if same object_id

Answer: equal (true) when same struct class and members match. Structs get value-based == for free: equal when the same struct class and all members match.

Which method lists a Struct's attribute names?

  • members
  • keys
  • fields
  • attributes

Answer: members. The class method members returns the attribute names, e.g. [:lat, :lng].

Are a plain Struct's attributes read-only?

  • Yes, always frozen
  • Only with keyword_init
  • No, they have writers and can be reassigned
  • Only the first one

Answer: No, they have writers and can be reassigned. A plain Struct gives read/write accessors, so p1.x = 10 works.

What must you do before using OpenStruct?

  • Nothing, it's built in
  • require 'ostruct'
  • require 'set'
  • include OpenStruct

Answer: require 'ostruct'. OpenStruct isn't loaded by default — add require 'ostruct' first.

What does an OpenStruct return for an attribute you never set?

  • An error
  • false
  • an empty string
  • nil

Answer: nil. OpenStruct returns nil for unknown attributes, which is why typos can hide silently.

What does Data.define (Ruby 3.2+) create?

  • A mutable struct
  • An immutable (frozen) value object
  • A hash subclass
  • A module

Answer: An immutable (frozen) value object. Data.define creates an immutable value object — instances are frozen with no attribute writers.

How do you 'change' a field on a Data.define instance?

  • Reassign it directly
  • Use a setter
  • Call obj.with(field: newvalue) for a fresh copy
  • It can't change at all

Answer: Call obj.with(field: newvalue) for a fresh copy. Data instances are frozen, so .with(field: value) returns a new copy with the change.