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] ※想定どおり