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
freezeis 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.