【Ruby】メソッドの引数は値渡し？参照渡し？

①〜④でreplaceメソッド実行後の変数aの値はそれぞれ何になるでしょうか。この記事ではメソッドの引数と、メソッド呼び出し元の変数について動きを追って見ていきます。

①破壊的メソッドによる変更

```ruby
a = "hoge"
replace(a)

def replace(str)
  str.upcase!
end
```

②破壊的メソッド実行後、値を変更

```ruby
a = "hoge"
replace(a)

def replace(str)
  str.upcase!

  str = "fuga"
end
```

③配列自体を変更

```ruby
a = ["hoge", "fuga"]
replace(a)

def replace(list)
  list = ["piyo", "fuga"]
end
```

④配列の要素を変更

```ruby
a = ["hoge", "fuga"]
replace(a)

def replace(list)
  list[0] = "piyo"
end
```

## メソッドの引数は値渡し？参照渡し？
メソッド内で引数の値を変更すると元の変数の値も変わるのが参照渡し、変わらないのが値渡しです。Rubyのメソッドは値渡しなのか参照渡しなのかを検証するために引数の値をみていきます。

### 値渡し？
もしメソッドの引数が値渡しであれば、```add(a)```実行後のaは2ではなく1のままになるはずです。

```ruby
a = 1
add(a)
puts a #=> 1 #変更されていない

def add(x)
  x += 1
end
```
aは2ではなく1になりました。addメソッド実行前後を比較して値が変わらない＝メソッドの引数は値渡しであるということがわかります。

### 参照渡し？
次に、④のメソッド内で配列自体を変更しているケースを考えてみます。メソッドの引数が値渡しであれば、a[0]は"hoge"のまま変わらないはず。

```ruby
a = ["hoge", "fuga"]
replace(a)
puts a #=>["piyo", "fuga"] #"piyo"に変わっている

def replace(list)
  list[0] = "piyo"
end
```

結果、a[0]は"piyo"になりました。ということはメソッドの引数は参照渡しなのでしょうか？

## 参照の値渡し
④のケースは参照渡しのように見えますが、実は値渡しです。ただし、値渡しで渡されているのはオブジェクトの参照です。object_idを確認すると、replace実行前のaと```list[0] = "piyo"```直前の引数xは同じobject_idになっています。
配列の要素の変更は破壊的メソッドのため、オブジェクトの値そのものが変わり、replace後のaに反映されます。そのため、あたかも参照渡しをしているように見えるのです。

```ruby
a = ["hoge", "fuga"]
puts a.object_id #=>70252646834320 
replace(a)
puts a.object_id #=>70252646834320　# replaceメソッド実行前のaと同じID

def replace(list)
  puts list.object_id #=>70252646834320 # aと同じID
  list[0] = "piyo"
  puts list.object_id #=>70252646834320 # aと同じID
end
```

では、なぜ「値渡し？」で確認したケースでは値が変わらなかったのでしょうか。
object_idの動きを確認してみます。

```add(a)```実行前のaと、```x += 1 ```直前のxは同じobject_idになっているので同じオブジェクトを参照しています。
```x += 1```を実行すると、xがもとの変数aとは別のオブジェクトに変わっています。
```x += 1```は非破壊的変更のため新しいオブジェクトが生成され、aには値が反映されなかったことがわかります。

```ruby
a = 1
puts a.object_id #=>3
add(a)
puts a.object_id #=>3

def add(x)
  puts x.object_id #=>3
  x += 1
  puts x.object_id #=>5 #>オブジェクトIDが変わっている
end
```

## 答え合わせ
では、①〜④のメソッド実行後のaの値を見ていきましょう。

**①破壊的メソッドによる変更**
これは④と同じで、破壊的メソッドによる変更を行なっているため、aの値が大文字の"HOGE"に変わります。

```ruby
a = "hoge"
puts a.object_id #=>70252646922080
replace(a)
puts a.object_id #=>70252646922080
puts a #=>"HOGE" #aが大文字の"HOGE"に変更された

def replace(str)
  puts str.object_id #=>70252646922080
  str.upcase! # strを大文字に変更
  puts str.object_id #=>70252646922080 #aと同じID
end
```

**②破壊的メソッド実行後、値を変更**
```str.upcase!```までの流れは①と同じです。が、②の場合は```str.upcase!```の後に```str = "fuga"```を実行しています。
```str = "fuga"```を実行するとオブジェクトIDが変わるため、aには```str.upcase!```までの値が反映され、"HOGE"になります。

```ruby
a = "hoge"
puts a.object_id #=>70252647097220
replace(a)
puts a.object_id #=>70252647097220
puts a #=>"HOGE"

def replace(str)
  puts str.object_id #=>70252647097220
  str.upcase!
  puts str.object_id #=>70252647097220

  str = "fuga"
  puts str.object_id #=>70252647132520 #オブジェクトIDが変わっている
end
```

**③配列自体を変更**
④はメソッド内で配列の要素を変更する例でしたが、こちらは配列そのものを変更するケースです。```list = ["piyo", "fuga"]```は破壊的な変更ではないため、新しくオブジェクトが生成され、オブジェクトIDが変わっています。つまり、もとの変数aには```list = ["piyo", "fuga"]```が反映されず、aの値は["hoge", "fuga"]のまま変わりません。

```ruby
a = ["hoge", "fuga"]
puts a.object_id #=>70252646933980
replace(a)
puts a.object_id #=>70252646933980
puts a #=>["hoge", "fuga"]

def replace(list)
  puts list.object_id #=>70252646933980
  list = ["piyo", "fuga"]
  puts list.object_id #=>70252646967500　#オブジェクトIDが変わっている
end
```

**④配列の要素を変更**
④のオブジェクトIDの動きは上で説明したとおりです。```replace(a)```後のaの値は["piyo", "fuga"]に変わります。

```ruby
a = ["hoge", "fuga"]
puts a.object_id #=>70252646834320 
replace(a)
puts a.object_id #=>70252646834320　# replaceメソッド実行前のaと同じID
puts a #=>["piyo", "fuga"]

def replace(list)
  puts list.object_id #=>70252646834320 # aと同じID
  list[0] = "piyo"
  puts list.object_id #=>70252646834320 # aと同じID
end
```

## まとめ
- Rubyのメソッドの引数は値渡しで参照を渡している（参照の値渡し）
- メソッドの引数に対して非破壊的な変更を行った場合、新しいオブジェクトが生成されるため、メソッドの呼び出し元の変数には値は反映されない
- メソッドの引数に対して破壊的な変更を行った場合、オブジェクトが持つ値自体が変更されるため、メソッドの呼び出し元の変数に値が反映される

## 参考
- [値渡しと参照渡しの違いを理解する](https://magazine.rubyist.net/articles/0032/0032-CallByValueAndCallByReference.html)
- [Rubyの破壊的メソッドと参照の値渡し - Qiita](https://qiita.com/metheglin/items/ca452bc0a3866d2de7bf)
- [Rubyの破壊的メソッドについて - Qiita](https://qiita.com/tentom/items/0164b68dff94702e3880)
- [【Ruby】メソッドの引数は参照の値渡し？ - blog @kimromi](https://kimromi.hatenablog.jp/entry/2015/05/11/204954)
- [引数の参照渡し（変数渡し）なのか単なるオブジェクトへの参照なのか、それが問題 - 新・日々録 by TRASH BOX@Eel](https://eel3.hatenablog.com/entry/20110214/1297690699#f-5ee81929)