1

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 Answers2

4

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?

iGian
  • 11,023
  • 3
  • 21
  • 36
1

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.

Amadan
  • 191,408
  • 23
  • 240
  • 301