はじめに
Rubyist Magazineでは、「参照の値渡し」が解説されています。
Rubyist Magazine
値渡しと参照渡しの違いを理解する
ただ、具体的かつ簡単なコードを題材にして理解したかったので、ざっくりと調べてみました。
(初学者が書くことなので間違い等があると思いますが、参考までにご覧ください)
不思議の共有
まず、「参照の値渡し」の前に、事情をよく分かっていない方にとっては不思議な事象を紹介します。
「参照の値渡し」を学びつつ、この不思議を解き明かしていきます。
irb(main):001:0> array = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> array2 = array
=> [1, 2, 3]
irb(main):003:0> array[0] = 100
=> 100
irb(main):004:0> array
=> [100, 2, 3]
# なんと、array2も変更されているではないか!
irb(main):005:0> array2
=> [100, 2, 3]
けど、このケースだと、もう一方の変数に変更がないんです。
私だけですかね、不思議じゃないですか?
irb(main):001:0> num = 100
=> 100
irb(main):002:0> num2 = num
=> 100
irb(main):003:0> num = 5
=> 5
irb(main):004:0> num
=> 5
# あれ、num2は100のままだ・・・
irb(main):005:0> num2
=> 100
object_idを活用してみる
では、事例を大幅に変更して、この問題を紐解いていきます。
代入するものをString型に変更します。そして、object_idというメソッドを使っていきます。
object_idというメソッドを使うと、オブジェクト固有のidを取得できます。
object_idメソッドを活用し、以下のケースにおいて、どのオブジェクトが参照されているか確かめます。
Object#object_id (Ruby 3.0.0 リファレンスマニュアル)
irb(main):001:0> name = 'dyson'
=> "dyson"
irb(main):002:0> name2 = name
=> "dyson"
irb(main):003:0> name.object_id
=> 70145544766620
irb(main):004:0> name2.object_id
=> 70145544766620
irb(main):005:0> name
=> "dyson"
irb(main):006:0> name2
=> "dyson"
上記のケースにおいては、'dyson'という値を渡しているのではないようです。
正確には、'dyson'のobject_idという値を代入しているようです。
参照するオブジェクトが同一なので、nameとname2の返り値は、'dyson'になります。
なお、String型は「打ち込む」度に新しいオブジェクトとして生成されます。
新しいオブジェクトなので、object_idは毎回変わります。
これは100などのInteger型のオブジェクトなどと異なります。
Integer型だけでなく、TrueClass, FalseClass, NilClass, Symbolなども同様です。
Object#object_id (Ruby 3.0.0 リファレンスマニュアル)
irb(main):001:0> 'dyson'.object_id
=> 70133892810380
irb(main):002:0> 'dyson'.object_id
=> 70133892796800
irb(main):003:0> 'dyson'.object_id
=> 70133892766900
irb(main):004:0> 100.object_id
=> 201
irb(main):005:0> 100.object_id
=> 201
irb(main):006:0> 100.object_id
=> 201
capitalizeとcapitalize!を活用してみる
capitalizeは非破壊的メソッドですが、capitalize!は破壊的メソッドです。
破壊的メソッドを使うと、そのオブジェクト自身に変更を加えることができます。
irb(main):001:0> name = 'dyson'
=> "dyson"
irb(main):002:0> name.capitalize
=> "Dyson"
irb(main):003:0> name
=> "dyson"
irb(main):004:0> name.capitalize!
=> "Dyson"
irb(main):005:0> name
=> "Dyson"
非破壊的メソッドcapitalizeを使ってみる
この違いを活用して、実験してみます。
irb(main):001:0> name = 'dyson'
=> "dyson"
irb(main):002:0> name2 = name
=> "dyson"
irb(main):003:0> name.object_id
=> 70233185108640
irb(main):004:0> name2.object_id
=> 70233185108640
irb(main):005:0> name
=> "dyson"
irb(main):006:0> name2
=> "dyson"
irb(main):007:0> name = name.capitalize
=> "Dyson"
irb(main):008:0> name.object_id
=> 70233185061420
irb(main):009:0> name2.object_id
=> 70233185108640
irb(main):010:0> name
=> "dyson"
irb(main):011:0> name2
=> "Dyson"
最終的に、nameは、object_idが70233185061420であるオブジェクトを参照しています。
しかし、name2は、object_idが70233185108640であるオブジェクトを参照しています。
nameに代入しているのは、nameをcapitalizeしてできた、新しいオブジェクトです。
nameとname2が参照するobject_idは異なるため、nameとname2の返り値も異なります。
破壊的メソッドcapitalize!を使ってみる
続いて、capitalize!メソッドを使って実験してみます。
capitalize!を使うと、オブジェクト自身に変更を加えることができます。
irb(main):001:0> name = 'dyson'
=> "dyson"
irb(main):002:0> name2 = name
=> "dyson"
irb(main):003:0> name.object_id
=> 70164238885440
irb(main):004:0> name2.object_id
=> 70164238885440
irb(main):005:0> name = name.capitalize!
=> "Dyson"
irb(main):006:0> name.object_id
=> 70164238885440
irb(main):007:0> name2.object_id
=> 70164238885440
irb(main):008:0> name
=> "Dyson"
irb(main):009:0> name2
=> "Dyson"
ここでは、‘dyson’というオブジェクト自体に変更を加えました。
name = name.capitalize!の実施前後でobject_idに変化がないことからも分かりますが、
'Dyson'という新しく生成したオブジェクトを代入したのではありません。
object_idが70164238885440であるオブジェクト自身に変更を加えたのです。
nameも、name2も、参照しているのは同一のオブジェクトです。
‘Dyson’というオブジェクトになったので、name及びname2の返り値が‘Dyson’になりました。
不思議を解き明かす
では、あえて解説もしませんが、これで不思議が分かったのではないでしょうか?
irb(main):001:0> array = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> array2 = array
=> [1, 2, 3]
irb(main):003:0> array.object_id
=> 70168202066140
irb(main):004:0> array2.object_id
=> 70168202066140
irb(main):005:0> array[0] = 100
=> 100
irb(main):006:0> array
=> [100, 2, 3]
irb(main):007:0> array.object_id
=> 70168202066140
irb(main):008:0> array2.object_id
=> 70168202066140
irb(main):009:0> array2
=> [100, 2, 3]
irb(main):001:0> num = 100
=> 100
irb(main):002:0> num2 = num
=> 100
irb(main):003:0> num.object_id
=> 201
irb(main):004:0> num2.object_id
=> 201
irb(main):005:0> num = 5
=> 5
irb(main):006:0> num.object_id
=> 11
irb(main):007:0> num2.object_id
=> 201
irb(main):008:0> num2
=> 100