Skip to content
AgileLogics
5 min read

Frozen Objects and Mutability

Prerequisites: String methods, Classes and objects

Some Ruby objects can be changed in place (mutable); others can't (immutable). Knowing which is which prevents a whole category of confusing bugs.

Variables hold references

A variable doesn't contain an object — it refers to one. Two variables can refer to the same object:

a = [1, 2]
b = a
b << 3
p a
p a.equal?(b)

c = a.dup
c << 4
p a
p c
[1, 2, 3]
true
[1, 2, 3]
[1, 2, 3, 4]

b = a doesn't copy the array; changing b changes a. dup makes a (shallow) copy.

The same applies to method arguments: a method that calls << or a bang method on an argument changes the caller's object.

freeze

freeze makes an object unchangeable. Any attempt to modify it raises FrozenError:

COLORS = %w[red green].freeze
p COLORS.frozen?

begin
  COLORS << "blue"
rescue FrozenError => e
  puts e.message
end
true
can't modify frozen Array: ["red", "green"]

Freezing constants is a good habit: a constant that can be modified isn't really constant.

Numbers, symbols, true, false, and nil are always frozen.

Frozen string literals

Adding this magic comment as the first line of a file freezes every string literal in it:

# frozen_string_literal: true

greeting = "hello"
p greeting.frozen?
loud = greeting.upcase   # fine: returns a new string
p loud

buffer = +""             # + gives a mutable copy
buffer << "a" << "b"
p buffer
true
"HELLO"
"ab"

Most Ruby projects put this comment at the top of every file (RuboCop adds it for you). Ruby 3.4 goes part of the way by default: string literals without the comment are "chilled" — they still work when modified, but Ruby can warn about it (run with -W:deprecated to see the warnings), because a future version intends to freeze them by default.

Common mistakes

  • freeze is shallow: freezing an array doesn't freeze the strings inside it.
  • Using << on a string argument inside a method and unexpectedly changing the caller's string. Build a new string with + or interpolation instead.