LoginSignup
69
51

More than 5 years have passed since last update.

Rubyの破壊的メソッドと参照の値渡し

Posted at

以下のようなコードを書いていて想定と違う動きだったので調べた。この辺全然理解してなかった。

val_ref_sample.rb
#!/usr/bin/env ruby

def normalized_name(origin_str)
  str = origin_str
  str.sub! '-', ''
  str.capitalize!
end

snack = 'caramel-corn'
p '元のString snackの値:' + snack
p 'normalized_nameの実行結果:' + normalized_name(snack)
p '元のString snackの値:' + snack
"元のString snackの値:caramel-corn"
"normalized_nameの実行結果:Caramelcorn"
"元のString snackの値:Caramelcorn"

メソッドの中でどんな好き勝手引数の値を変更しても、メソッドに渡す前のトップレベルで定義されているsnackの値は変わらないと思い込んでたけど、そうではない。

Rubyは値渡しだが、初級者がひっかかりやすいのは参照の値渡し(共有渡し)

引数に渡す前の変数がかわってしまうという挙動を見て、参照渡しなのか?と思ってRubyistMagazineで調べたら、

Ruby (や多くのプログラミング言語) は値渡しである。

と明確に書かれているが、

初級者が特にひっかりやすい「参照の値渡し」

とも書かれていて見事にひっかかってました。

■object_idで確認
rubyのオブジェクトに実装されているobject_idメソッドを使うと、オブジェクト固有のidを取得できる。これによって同じオブジェクトを参照しているのか、別のオブジェクトを参照しているのか確認できる。

■参照の値渡しサンプル

# 'test'というStringオブジェクトを作成しstrに代入
irb(main):001:0> str = 'test'
irb(main):002:0> str.object_id
=> 69917655324020

# strの参照先をstr2にコピー(参照の値渡し)
irb(main):003:0> str2 = str
irb(main):004:0> str2.object_id
=> 69917655324020 # さっきと同じobject_id

# どこからも参照されてない'test'という新しいStringオブジェクトを作成
irb(main):005:0> 'test'.object_id
=> 69917655229860 # 新しいobject_id

# さらに'test'という新しいStringオブジェクトを作成し、str2の向き先を変更
irb(main):006:0> str2 = 'test'
irb(main):007:0> str2.object_id
=> 69917655167360 # 新しいobject_id

スクリーンショット 2014-11-02 17.45.08.png

次のように解釈したら矛盾がなさそうなので、そう理解した。
Rubyは値渡しではあるけども、Rubyにおける値はすべてがオブジェクトなので、実際には変数が持つのはオブジェクトへの参照である(RubyistMagazineの説明におけるメモリアドレス)。変数が値自体を持つことはない。
なので、実質Rubyは参照の値渡しである。
(と解釈しましたがもし誤解や例外などあれば教えてください!!)

代入とreplace

代入は、変数の向き先(参照先)を変更する。=演算子で実行。
replaceは、参照先のオブジェクトが保持する値自体を変更する。rubyの破壊的メソッドはreplaceを使っているらしい。
※ちなみに、FixnumなどのImmutableオブジェクトは破壊的な変更はできない。(replaceメソッドも用意されてない)2014や1986などのFixnum型を何回作成しても同じオブジェクトを参照する。

■replaceのサンプル

# 'test'というStringオブジェクトを作成し、strに代入し、その参照先をstr2にもコピー(上の例と同じ)
irb(main):001:0> str = 'test'
irb(main):002:0> str.object_id
=> 69907653151280
irb(main):003:0> str2 = str
irb(main):004:0> str2.object_id
=> 69907653151280

# str2が参照するオブジェクト自体の値をreplaceによって変更する
irb(main):005:0> str2.replace 'experiment'
=> "experiment"

# str2変数の中身だけでなく、strの中身もかわる
irb(main):006:0> str2
=> "experiment"
irb(main):007:0> str
=> "experiment"

スクリーンショット 2014-11-02 17.47.20.png

■一番はじめの例のおさらい

スクリーンショット 2014-11-02 18.02.14.png

実行環境

% ruby -v
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]
69
51
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
51