Can anyone explain why a string would mutate unexpectedly. My mentor makes a bid deal about this but I dont really understand why.
-
See this: https://stackoverflow.com/questions/27702540/why-are-there-frozen-constants-everywhere ant this (not Ruby but the concept should be the same): https://stackoverflow.com/questions/33124058/object-freeze-vs-const – iGian Sep 05 '18 at 06:48
2 Answers
An example on how even an object referenced by a constant can mutate:
FOO = 'foo'
p FOO # => "foo"
FOO.upcase!
p FOO # => "FOO" (no errors)
BAR = 'bar'.freeze
p BAR # => "BAR"
BAR.upcase! # => freeze.rb:8:in `upcase!': can't modify frozen String (RuntimeError)
freeze
the object referenced by the constant can prevent unexpected behaviour of the code, so a developer can fix it or whatever.
To see that freeze
freezes just the object, see that if a new value is assigned to the constant, the error thrown is different:
BAZ = 'baz'.freeze # => warning: previous definition of BAZ was here
BAZ = 'bazbaz' # => warning: already initialized constant BAZ
Inspired by this topic: Why are there frozen constants everywhere?

- 11,023
- 3
- 21
- 36
Here are some methods that mutate strings: <<
, gsub!
, downcase!
, replace
...
Let's say there is a website, "Chitter", where users post "cheets" about their daily life. Let's assume Chitter account names are normal names, just lowercased and with underscores instead of spaces (which is silly, but this is a silly example, so just roll with it). You want to get a user's cheets via the website's API; you want to display "Hello, Amadan! Here are your cheets:" and show you what it found. You look around, and there's a library for it! Awesome. So you gem install
it and start coding:
require 'chitter'
print "What is your name? "
name = gets
chitter = Chitter.login_by_name(name)
puts "Hello, #{name}! Here are your cheets:"
puts chitter.cheets
I input Amadan
, expecting Hello, Amadan
- but out comes Hello, amadan
, lowercased! How could that happen?
It seems that our imaginary Chitter
gem had this line in its login_by_name
:
name.gsub!(' ', '_').downcase!
Here you go, unexpected string mutation. To be sure, the fault is entirely on me, the author of the imaginary Chitter gem, because if that line was
name = name.gsub(' ', '_').downcase
or even
name = name.dup.gsub!(' ', '_').downcase!
there would have been no problem, the user wouldn't have been insulted by decapitalisation. But if their test suite included Chitter.login_by_name("testname".freeze)
, their test would experience an exception when those mutators tried their mutating.
The same thing might work in reverse. Take this snippet for example:
require 'chitter'
print "What is your name? "
name = gets
chitter = Chitter.login_by_name(name)
best_friend = chitter.best_friend
best_friend.name.upcase!
puts "Shoutout to your BEST FRIEND, #{best_friend.name}! Here's some of their cheets:"
puts chitter.best_friend.cheets
And pandemonium erupts, because suddenly your best friend doesn't have a Cheeter account! Why? Because apparently someone forgot to freeze
the name
inside the object returned by chitter.best_friend
. The user then modified the said name by upcase!
, and then the library tried to access the cheets by the uppercased name, and failed. The problem's root is
class Cheeter::User
def initialize(name)
@name = name
end
...
attr_reader :name
...
end
If the initialization said @name = name.freeze
, then we couldn't upcase!
it. If we had this method instead of attr_reader
:
def name
@name.dup
end
then running upcase!
on it would not matter, the name that the library works with would not be changed. But since cheeter.best_friend.name
returned exactly the object that it was working with, and didn't freeze
it, we threw a monkey wrench into its operation when we mutated it, thinking it's ours to do with as we please.
EDIT: It seems "Chitter" actually exists. Rather than rename my example and risk hitting another real application, my apologies to Chitter creator, all similarities are coincidental, and I am certain that the real Chitter isn't so horribly designed as mine.

- 191,408
- 23
- 240
- 301