モチベーション

Rubyの関数の引数は値渡しである(参照渡しではない)ことを証明する

背景

プログラム言語ごとに、関数の引数が値渡しであったり参照渡しであったりと異なる。Rubyの記事を見ると、Rubyは参照渡しであるという記事を散見するが断言させてもらう。Rubyは値渡しである。アドレス番地などを用いた説明もできるがそれらは本記事の参考に譲り、ここではコードの振る舞いベースで値渡しであることの証明を行う。

変数が格納するのはオブジェクトを指すポインタのようなもの

整数を格納した変数を、別の変数に代入してそれらのobject idを確認してみる。

irb(main):001:0> val = 10
=> 10
irb(main):002:0> val2 = val
=> 10

irb(main):004:0> val.object_id
=> 21
irb(main):005:0> val2.object_id
=> 21

これをみると、2つの変数のobject idは同値になっているため、変数には値が入っているのではなく、オブジェクトへの参照(ポイントのようなもの)が入っていることがわかる。

配列を関数の引数にしてみる

配列を関数の引数にして、関数内で変更するコードを書いてみる。

def display_obj_id(arg)
  puts "OID before assign: #{arg.object_id}"
  arg[0] = 10
  puts "OID after assign: #{arg.object_id}"
  arg
  # arg = [3,3,3]
  # puts arg.object_id
end

array = [1,2,3]
puts "array before func: #{array}"
puts "OID outside of func: #{array.object_id}"
puts "array after func : #{display_obj_id(array)}"
puts "array outside of func : #{array}"

これを実行する。

# 実行結果
$ ruby check_ob.rb
array before func: [1, 2, 3]
OID outside of func: 70235357240240
OID before assign: 70235357240240
OID after assign: 70235357240240
array after func : [10, 2, 3]
array outside of func : [10, 2, 3]

関数外と関数内でのobject idが同値であることから同一オブジェクトへの参照が値渡しされていると言える。なので、関数内でarg[0] = 10とすると配列オブジェクトが指す値が変わり、関数の外で配列の値を確認すると変更されていることが確認できる。

整数を関数の引数に渡すケースは?

整数を渡す場合でも同様に、関数の引数に渡るのは整数オブジェクトの参照の値渡しが行われる。その様子を以下のコードで確認する。

def display_obj_id(arg)
  puts "OID before assign: #{arg.object_id}"
  arg = 10
  puts "OID after assign: #{arg.object_id}"
  arg
end

int =  1
puts "int before func: #{int}"
puts "OID outside of func: #{int.object_id}"
puts "array with func : #{display_obj_id(int)}"
puts "array outside of func : #{int}"

実行結果はこの通り。

$ ruby check_ob.rb
int before func: 1
OID outside of func: 3
OID before assign: 3
OID after assign: 21
array with func : 10      # 関数内で値が変わる
array outside of func : 1 # 関数の外で値が戻る

配列の時と同様に、関数の外のobject idと中のobject idが同じであり、参照の値渡しが行われている。配列の時と少し違うのはarg = 10とした時に、object idが変わっているところ。

ここは、引数ので渡した参照の値が指すオブジェクトを変更しているのではなく、新しいオブジェクトを指す参照を関数内のローカル変数に代入しているためだ。クドいようだがこれを以下のようにして確認する。

➤  irb
irb(main):001:0> int = 1
=> 1
irb(main):002:0> int.object_id
=> 3
irb(main):003:0> 100.object_id
=> 201

変数intのobject id が3であるのに対して、100.object_idと整数のobject idを調べると変数intと異なるものを指している。

これより、先ほどの関数内のarg = 10の挙動が関数内のローカル変数に新しいオブジェクトへの参照を代入していることがわかる。また、先ほどの配列の例ではarg[0] = 10としたが、arg = [10,2,3]としていれば同様の結果になる。

このようにRubyはオブジェクトへの参照が引数に値渡しで利用されている。

参考