Mutable, frozen and copied objects
Frozen values
A Ruby string and a Ruby hash can be changed after they are created, which is convenient and also the source of many bugs: a method that upcases a string in place changes the caller's value too. freeze turns an object read-only, and the change raises instead of happening quietly:
ruby -e 's = "a".freeze; puts s.frozen?'
true
ruby -e 'h = {a: 1}.freeze; h[:b] = 2'
-e:1:in '<main>': can't modify frozen Hash: {a: 1} (FrozenError)
The method that mutates a string is upcase! with a bang; the version without the bang returns a new string and leaves the original alone. Freezing is the safety net for the cases where the bang would be a mistake.
dup and clone
Copying a frozen object gives a mutable one. The two copy methods differ in one detail: clone keeps the frozen state, dup does not.
ruby -e 's = "a".freeze; t = s.dup; puts t.frozen?; puts s.clone.frozen?'
false
true
Use dup when you want a working copy, and clone when the copy should behave like the original. Both are shallow: a frozen array of strings still holds the same strings, and freezing the array does not freeze them.
The magic comment
Add this line at the top of a file and every string literal in that file is frozen:
# frozen_string_literal: true
name = "Ada"
puts name.frozen?
ruby frozen.rb
true
The comment works only as the first line of a file. In a -e one-liner it is an ordinary comment, and the literal stays mutable:
ruby -e 'puts "Ada".frozen?'
false
The point is allocation: a frozen literal can be shared between calls, while a mutable one allocates a new string each time the line runs. A method that is called a million times saves a million small allocations, which is why many projects put the comment at the top of every file and keep mutation for explicit cases.
What to watch for
Freeze what leaves a method as a constant: a lookup table, a list of
allowed values, a configuration hash. A caller that changes it changes the
behaviour of every other caller.
A frozen hash or array is shallow. freeze the
outer object and the inner strings are still mutable; deep-freeze them
one by one when it matters, and do not expect
freeze to protect the contents.
Next: Integers, floats and rounding
Article author: Arthur Isaev