1. Technology
You can opt-out at any time. Please refer to our privacy policy for contact information.

Can't Modify Frozen String

By

Can't Modify Frozen String

Question

When I try to run the following code, an exception is thrown stating "cannot modify frozen string." What is a frozen string? How can I get around this?


# Simulate returning a string from an API
str = "test string".freeze

# Try to modify it, this will raise exception
str.gsub!(/test/, 'tasty')

Answer

Freezing objects is a feature that's easy to forget in Ruby. Any object in Ruby can be frozen by calling its freeze method. Once an object is frozen, any further modifications to that object will raise an exception as in the example above. This includes any modifications to the internal data in a special type (Strings are special types, implemented in C), or anything that changes the instance variables. It makes the object immutable.

If you have an object you think might be frozen, you can call the frozen? method. It will return true if the object is frozen, else it will return false.

In the above example, a (pretend, in the example code) API returns a frozen string. The gsub! method tries to make a modification to the internal String data and, since it's frozen, this will raise the exception. So, now that you know what's going on here, how do you get around it?

Depending on what you had intended to do with this string, you can try duplicating it. If you call str.dup, it will duplicate the string and the duplicate will not be frozen. You can then do whatever you want to this string. Or, you can use the gsub method, which does just that: it creates a duplicate of the string before doing any substitutions. So, you can do str.dup.gsub!(/test/, 'tasty'), or str.gsub(/test/, 'tasty'), both of them do roughly the same thing (if not exactly the same thing, depending on your implementation.

However, this will only change the string data in the duplicate. If your intent was to change the string data in place so anywhere the string is used the data will be changed (for example, changing the path to a file the API needs to access), then you may be out of luck. There is no unfreeze or thaw method. Once objects are frozen, they're stuck forever. It's possible an API would freeze strings and other objects in an attempt to force you to use the API and not corrupt its encapsulation. This, however, just makes it harder to monkeypatch things when something doesn't work. But let's take a look at what really happens when objects are frozen.

Take a hash with a single key and string value. Freeze it. Your challenge is to modify the contents of the string value.


config = { :file => 'config.cfg' }.freeze

The first you may try is the obvious, config[:file] = 'another.cfg'. That won't work, the freezing catches it. You can try doing config.dup[:file] = 'another.cfg', but remember, we're monkeypatching here. All the API internals are still going to refer to the original config hash. So, let's just change the string data. In the first attempt, we were trying to assign a completely new string object to the hash. What if we keep the string object where it is and just change its internal C data using gsub!? We can do config[:file].gsub!(/^.*$/, 'another.cfg'). There we go, now we're getting somewhere.

That little bit of regular expression magic just replaced all of the string's data with the substitution string in place, without creating another string or modifying what the hash refers to. When you freeze a hash, it just freezes the references to the objects it already has, it doesn't actually freeze the objects themselves. The frozen hash won't know the difference here. This is an important thing to understand about freezing objects, it's not recursive. All freezing does is prevent the references the object holds from being modified. In some cases, especially in classes implemented in C, there may be some special checks to enforce this, but it's kind of hard to police objects other than the object itself.

So, there you have it. You understand a bit more about what happens when you freeze objects, and you know two things to try when an object you want to manipulate is frozen. If it's appropriate to make a copy, the copy of the object won't be frozen. If you must modify the object in place then try to modify the objects held within that frozen object without changing the references the frozen object holds. Or, if worst comes to worst, find where the object is frozen and comment that code out. That's not really recommended, but if you need something to work and that's the only way, then that's what you're going to have to do.

  1. About.com
  2. Technology
  3. Ruby
  4. Question and Answer
  5. Can't Modify Frozen String

©2014 About.com. All rights reserved.