Webエンジニアの備忘録

およそ自己の作業メモとして残しております。

rubyのメソッド引数が値渡しという話

Rubyを書き始めてまだ2〜3週間ですが、メソッドで思わぬ挙動があったので記録しておきます。

挙動が想定外だった

def test_add(arr)
  arr += [1]
  puts 'B=' + arr.to_s
end

def test_push(arr)
  arr.push(1)
  puts 'D=' + arr.to_s
end

arr = [0, 2]
puts 'A=' + arr.to_s

test_add(arr)
puts 'C=' + arr.to_s

test_push(arr)
puts 'E=' + arr.to_s

# => A=[0, 2]
# => B=[0, 2, 1]
# => C=[0, 2]
# => D=[0, 2, 1]
# => E=[0, 2, 1]  ※想定外

おや?っと思ったのはEなのですが、メソッド外でarrにtest_push()処理値が反映されていました。 Rubyのメソッドは「すべて値渡し」というふうに記憶していたからです。

オブジェクトIDによる確認

Rubyはすべての値がオブジェクトなので、オブジェクトIDを確認することにしました。

def test_add(arr)
  arr += [1]
  puts 'B=' + arr.object_id.to_s
end

def test_push(arr)
  arr.push(1)
  puts 'D=' + arr.object_id.to_s
end

arr = [0, 2]
puts 'A=' + arr.object_id.to_s

test_add(arr)
puts 'C=' + arr.object_id.to_s

test_push(arr)
puts 'E=' + arr.object_id.to_s

# => A=69943959716760
# => B=69943959716500  ※これだけ異なる
# => C=69943959716760
# => D=69943959716760
# => E=69943959716760

値渡しと言いながら、メソッド内外で利用されているオブジェクトIDが同じでした。 一点興味深かったのがarr += [1]をおこなったタイミングで新規オブジェクトが作成されている点です。

Rubyのメソッド引数はオブジェクトIDを値渡ししている

見出しの通り解釈することにしました。 そもそも参照渡しという解釈はメモリ上のポインタの話をした際に出てくるものなので、そこと混同すると参照渡しじゃないのか、という話になってくるのかと思いました。

オブジェクトをコピーして操作する

こう書けば想定通りでした。

def test_add(arr)
  arr += [1]
  puts 'B=' + arr.to_s
end

def test_push(arr)
  arr = arr.dup.push(1)
  puts 'D=' + arr.to_s
end

arr = [0, 2]
puts 'A=' + arr.to_s

test_add(arr)
puts 'C=' + arr.to_s

test_push(arr)
puts 'E=' + arr.to_s

# => A=[0, 2]
# => B=[0, 2, 1]
# => C=[0, 2]
# => D=[0, 2, 1]
# => E=[0, 2]  ※想定どおり