Ruby 2.7 adds FrozenError#receiver


Ruby 2.7 has added FrozenError#receiver to return the frozen object on which modification was attempted. It is similar to NameError#receiver. This can help in pinpointing the frozen object.

Before

In Ruby, we use freeze on objects, to make sure objects are not allowed to be mutated by others. If we try to modify such a frozen object, it will throw a FrozenError.

a = [].freeze
#=> []
a << 1 rescue (e = $!)
#=> #<FrozenError: can't modify frozen Array>

But with this error, its not easy to simply identify or perform some cleanup operations on the frozen object

We can also manually initialize and throw a FrozenError:

raise FrozenError.new("Error Message")

FrozenError#receiver

When we try to modify frozen object, it gives FrozenError. With this error, we can pinpoint the frozen object by calling FrozenError#receiver method.

a = [].freeze
#=> []
a << 1 rescue (e = $!)
#=> #<FrozenError: can't modify frozen Array>
e.receiver
#=> []

While initializing the FrozenError, we can pass the frozen object as the second argument.

# Trying to initialize FrozenError with receiver as second argument
error = FrozenError.new("Error Message", [].freeze)
#=> #<FrozenError: Error Message>
error.receiver
#=> []

Usage

FrozenError#receiver gives us the flexibility to handle the FrozenError exception specific to receiver, in a graceful manner.

# Defining sub_scorer which is used at multiple places
# and based on some condition it is updating scores
def sub_scorer(scores)
  ...
  scores << 50 if condition
  ...
end

# Defining final_scorer which may call sub_scorer
def final_scorer
  begin
    scores = [10, 20, 30].freeze
    sub_scorer(scores)
  rescue FrozenError => e
    # We can now gracefully handle Frozen object violations
    # based on the receiver
    if e.receiver == scores
      return "Can not modify scores"
    else
      return "Can not modify frozen objects"
    end
  end
end

final_scorer
#=> "Can not modify scores"