はじめに
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