Rubyは参照の値渡し

Ruby は基本的にすべてがオブジェクトで、またすべてが「参照の値渡し」です。ただし数値とシンボルは immutable なので、「値渡し」のように振舞います。

注意すべきは文字列です。

a = "Hello"
p a.object_id   #=>23440680
b = a
p b.object_id   #=>23440680

b += ", world!"
p b             #=>"Hello, world!"
p b.object_id   #=>23440560
p a             #=>"Hello"
p a.object_id   #=>23440680

b = a をやっていますが、b += ", world!" のところで別のオブジェクトが生成されているので、a を出力しても "Hello" になります。一見「値渡し」のようです。けれども、

a = "hello"
p a    #=>"hello"
b = a
b.capitalize!
p a    #=>"Hello"

の場合、変更を加えたのは変数b ですが、capitalize!メソッドは別のオブジェクトを生成しないので、変数a も変更されるように見えます。これが Ruby が「参照の値渡し」である証拠です。特にメッソド内で(引数によって与えられた)文字列を操作する場合は、注意が必要になります。気になる場合は、dupメソッドでオブジェクトをコピーしておけば安全です。あるいは、不用意に変更したくないオブジェクトは、freeze しておくかです。


配列の場合でも同様です。

a = [0, 1]
p a    #=>[0, 1]
b = a
b.push(2)
p a    #=>[0, 1 ,2]

これを防ぎたければ、b = a の代りに b = a.dup としなければなりません。(※注意


自分で作ったクラスのオブジェクトも、当然すべて「参照の値渡し」です。


※注意
『Effective Ruby』(邦訳p.62)にこうあります。

Array のようなコレクションクラスの場合、これは[dup や clone では]コンテナのコピーは作られるが、要素のコピーは作られないということだ。オリジナルのコレクションに影響を与えずに要素を追加、削除はできるが、要素自体についてはそうは言えないのである。オリジナルのコレクションとコピーは、ともに同じ要素オブジェクトを参照している。要素を書き換えると、両方のコピーからもそれが見えてしまう。

つまり、こういうことです。

a = ["Polar"]
b = a.dup << "Bear"
b.each {|x| x.sub!('lar', 'oh')}
p a     #=>["Pooh"]

こうした場合には「浅い」コピーの dup は無力なので、自分で「深い」コピーの処理を書かねばなりません(ちなみに、ここで配列afreeze してもエラーは出ません)。前掲書には、Marshalモジュールを使った、多少トリッキーな対処法が載っています(b = Marshal.load(Marshal.dump(a)))。配列の深いコピーをする組み込みメソッドがないのは、あまりない操作だし、素直に配列の深いコピーをするとメモリを大幅に喰うからでしょうかね。

Marshal を使わなければ、こんなふうにやったらどうでしょうか。キモは2行目です。

a = ('a'..'d').to_a
b = a.map {|x| x.dup}     #深いコピー
p b.map {|x| x.upcase!}   #=>["A", "B", "C", "D"]
p a                       #=>["a", "b", "c", "d"]

ただ、多次元配列とかだと、ダメかも知れない。そのうち調べてみます。(6/14追記)
 

3年後の追記

上の説明はまだあまりわかっていない感じですが、一応そのままにしておきます。ただ、

Ruby は基本的にすべてがオブジェクトで、またすべてが「参照の値渡し」です。ただし数値とシンボルは immutable なので、「値渡し」のように振舞います。

というのはやはり正しいです。

メソッド呼び出しは「参照の値渡し」であることに注意が必要です。特に配列や文字列を引数にしてメソッド内部でオブジェクトの「破壊的変更」をすると、メソッド呼び出しの方でも値が変ってしまいます。文字列の例。

def cp(string)
  p string.capitalize!
end

a = "tokyo"
cp(a)    #=>"Tokyo"
p a      #=>"Tokyo"

配列の例。

def double(ar)
  ar[1] *= 2
  return ar
end

ar = [2, 4, 6]
p double(ar)    #=>[2, 8, 6]
p ar            #=>[2, 8, 6]

 
代入も「参照の値渡し」です。

a = "kyoto"
b = a
a.capitalize!
p b    #=>"Kyoto"

 
破壊的変更でないメソッドを使うと、別のオブジェクトが生成されます。

a = "kyoto"
b = a.capitalize + " University"    #=>これらは破壊的変更でない
p a.upcase!    #=>"KYOTO"
p b            #=>"Kyoto University"

p a.object_id    #=>47415064424700
p b.object_id    #=>47415064464080

以下のちがい、わかりますか? 与える出力は同じですが、大きくちがう部分があります。

a = "nagoya"
a.capitalize!
p a                  #=>"Nagoya"

b = "nagoya"
b = b.capitalize
p b                  #=>"Nagoya"

変数a はメソッド処理ののちでも object id が変わりません。しかし、変数 はメソッド処理して再代入すると object id が変わります。これは大きなちがいです。これがわからないと、奇妙なバグを作ることがあるので注意しなければなりません。(2018/7/10)