JQ Blog

Rubyの値渡しと参照渡し

一般的に、引数は二つの方法がある。一つが値渡しで、もう一つが参照渡しである。
値渡しの概念はとても簡単。

  1. 受け取るデータを格納する変数 (仮引数) を用意する。
  2. データを引数に代入する。
  3. 関数の実行終了後、引数を廃棄する。

値渡しのポイントは2番目。データを引数に代入するとき、データのコピーが行われる。

1
2
3
4
5
6
7
8
9
10
11
irb> def foo(a)
irb> a = 100
irb> print a
irb> end
=> :foo
irb> x = 10
=> 10
irb> foo x
100=> nil
irb> x
=> 10

fooが参照渡しであれば、仮引数aの値を100に書き換えると、実引数であるxの値も100になるはず。foo(x)を呼び出したあと、xの値は10のままなので、Rubyは「値渡し」であることがわかる。

1
2
3
4
5
6
7
8
9
10
11
12
13
irb> def print_obj_id arg
irb> p "before change : #{int.object_id}"
irb> arg = 10
irb> p "after changed : #{int.object_id}"
irb> arg
irb> end

irb> int = 1

irb> p "int before func : #{int}"
irb> p "obj_id outside func : #{int.object_id}"
irb> p "excute func : #{print_obj_id int}"
irb> p "int after excuted func : #{int.object_id}"

このコードに対する結果は下記のようになる。

1
2
3
4
5
6
"int before func : 1"
"obj_id outside func : 3"
"before change : 3"
"after changed : 21"
"excute func : 10"
"int after excuted func : 3"

これは渡された仮引数のargが変わっても実引数の変数intは変わらないことを証明する。
しかし、配列、hashなどはmutable(書き換え可)なオブジェクトなので、関数の引数に配列やhashを渡してそれを修正すると、呼び出し元の変数の値も書き換えられるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# arrayの場合
irb> def bar(x, y)
irb> x.push y
irb> x[0] = 10
irb> end
=> :bar
irb> a = [1, 2, 3]
=> [1, 2, 3]
irb> a.object_id
=> 70284299180520
irb> bar a, 4
=> 10
irb> a
=> [10, 2, 3, 4]
irb> a.object_id
=> 70284299180520

# hashの場合
irb> def baz x, y
irb> x.store "fourth", y
irb> x[:first] = 10
irb> end
=> :baz
irb> hash = {first: 1, second: 2, third: 3}
=> {:first=>1, :second=>2, :third=>3}
irb> baz hash, 4
=> 10
irb> hash
=> {:first=>10, :second=>2, :third=>3, "fourth"=>4}

上記のコードを見てみるとarrayの場合はarray変数の0番目indexの値が変わり、端っこにデータが追加されたのが確認できる。hashの場合もfirstのオブジェクトが変わり、端っこにデータが追加されたのが確認できる。

追記

オブジェクトはどうなるかについても試してみたので追記。
下記のようなクラスを作成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Name
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def first_name(name)
    @first_name = name
    @first_name
  end

  def last_name(name)
    @last_name = name
    @last_name
  end

  def full_name
    full_name = "#{@last_name} #{@first_name}"
  end
end

Nameのオブジェクトを作ってfull_nameのメソッドを実行すると

1
2
3
4
irb> create_name = Name.new "Nobunaga", "Oda"
=> #<Name:0x007f8483c1f8b8 @first_name="Nobunaga", @last_name="Oda">
irb> create_name.full_name
=> "Oda Nobunaga"

普通に入力した通りOda Nobunagaというフルネームが取得される。
ここで下記のようなクラスを作成して試してみる。

1
2
3
4
5
6
7
8
9
10
class Change
  def initialize(obj)
    @name_obj = obj
  end

  def change_str
    @name_obj.first_name("hogehoge")
    @name_obj.last_name("foo")
  end
end

このChangeクラスにcreate_nameオブジェクトを渡して実装してみると

1
2
3
4
5
6
7
8
9
10
irb> create_name = Name.new "Nobunaga", "Oda"
=> #<Name:0x007f8483c1f8b8 @first_name="Nobunaga", @last_name="Oda">
irb> create_name.full_name
=> "Oda Nobunaga"
irb> change = Change.new create_name
=> #<Change:0x007f84838aae58 @name_obj=#<Name:0x007f84838c0898 @first_name="Nobunaga", @last_name="Oda">>
irb> change.change_name
=> "foo"
irb> create_name.full_name
=> "foo hogehoge"

create_name.full_nameを叩いた時に値が変わることが確認できる。
では、create_nameChangeクラスの@name_objの変数が同じものなのかな。確認してみよう。

1
2
3
4
5
6
7
8
9
10
11
12
class Change
  attr_accessor :name_obj

  def initialize(obj)
    @name_obj = obj
  end

  def change_name
    @name_obj.first_name("hogehoge")
    @name_obj.last_name("foo")
  end
end

Changeクラスにattr_accessorを追加して@name_objを使えるようにする。

1
2
3
4
5
6
7
8
9
10
11
12
irb> create_name = Name.new "Nobunaga", "Oda"
=> #<Name:0x007f8483b03740 @first_name="Nobunaga", @last_name="Oda">
irb> change = Change.new(create_name)
=> #<Change:0x007f8483ae06c8 @name_obj=#<Name:0x007f8483b03740 @first_name="Nobunaga", @last_name="Oda">>
irb> create_name === change.name_obj
=> true
irb> create_name.equal? change.name_obj
=> true
irb> create_name.object_id
=> 70103560887200
irb> change.name_obj.object_id
=> 70103560887200

create_name@name_objは同じものだったそうだ。
ということで、オブジェクトも参照の値渡しということが分かってきた。

参照

お気楽 Ruby プログラミング入門
Rubyの関数の引数は値渡しであることの証明をする
メソッドの引数(値渡し、参照渡し)