Reassigning a method argument never touches the caller
Question
What is the return value of the following Ruby code?
def my_method(a)
a += [4]
end
a = [1, 2, 3].freeze
my_method(a)
a # => ???
The correct answer is
-
[1, 2, 3, 4] -
It raises
FrozenError -
[1, 2, 3]Correct
Explanation
TL;DR
Inside the method, a += [4] expands to a = a + [4]. Array#+ returns a new Array, and the assignment rebinds the method-local parameter a to it. The caller's variable still points at the frozen [1, 2, 3], which was never mutated: no FrozenError, and a is still [1, 2, 3].
Step by step
def my_method(a)
a += [4]
end
a = [1, 2, 3].freeze
my_method(a) # => [1, 2, 3, 4]
a # => [1, 2, 3]
a.frozen? # => true
Two separate mechanisms are at work.
First, Array#+ never mutates its receiver. It builds a fresh Array, so calling it on a frozen array is perfectly legal:
a = [1].freeze
b = a + [2] # => [1, 2]
a # => [1]
b.equal?(a) # => false
b.frozen? # => false
Second, the parameter is a private copy of the reference. A variable in Ruby is a label that points to an object. When you pass a variable to a method, Ruby creates a new local variable, the parameter, pointing to the same object:
def same_object?(arg, original)
arg.equal?(original)
end
var = Object.new
same_object?(var, var) # => true
Reassigning the parameter only rebinds that method-local label; the caller's variable is untouched:
def rebind(arg)
arg = [99]
arg
end
var = [1]
rebind(var) # => [99]
var # => [1]
Edge cases
The private copy protects the caller's variable, not the object. Mutating the object through the parameter is visible to the caller:
def mutate(arg)
arg << 4
end
var = [1, 2, 3]
mutate(var) # => [1, 2, 3, 4]
var # => [1, 2, 3, 4]
And on a frozen array, in-place mutation is exactly what raises:
def mutate(arg)
arg << 4
end
begin
mutate([1, 2, 3].freeze)
rescue FrozenError => e
e.message # => "can't modify frozen Array: [1, 2, 3]"
end
The rule of thumb: += on a parameter rebinds a local and the caller sees nothing; << mutates the shared object and the caller sees everything.
Share this quiz
Comments
No comments yet. Be the first.