LaunchKit
All quizzes
Ruby 0 views

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

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.

Only used to confirm and publish your comment. Never shown publicly, never shared.

Markdown: **bold**, `code`, ```fenced blocks```, > quotes, [links](url). HTML and images are not rendered.