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追記)