RubyOnBasis[03] 新人Railsエンジニア向け講座

このシリーズについて

RubyOnBasisはRuby on Railsを業務に利用する新人Webエンジニアのための、Railsを使わないテキストだ。この講座ではWEBrickを使ってWebアプリを書きながらHTTPを始めとする基礎知識をキャッチアップしてRailsがどうやって作られ動くのかを理解していく。

このシリーズを始めから読む

ルビィの奇妙な変数

早速だがこんなコードを実行してみよう。どんな結果になるかな?

cat = 'にゃーん'
animal = cat
animal.slice!(1..-1) # Strig#slice!は文字列の指定位置を削除する
puts cat
# => "に"

んん? これはどういうことだろう。変数animalは変数catをコピーして新しく作ったのに、animalの中身が変更されるとcatまで変わってしまった!

説明しよう。よく変数はデータを入れる箱だとか説明される。聞いたことがあるよね。それはわかりやすくした例えた、少し不正確だし概念だけの話だ。

ここでは実態について考えてみよう。まず、変数が初めてコード上に書かれた際に何が起こるかだ。

ハッピーバースディ!

変数が初めてコード内に登場するとメモリ上にデータを保存する領域を確保してそのアドレス(位置を特定する情報)が変数が紐づけられる。変数自身が持っているのはあくまでもメモリ上のアドレスだけだ。実際のデータ、それがどんなクラスでどんな状態なのか?はメモリ上に格納されている。例えるならWebページとurlの関係と同じだ、変数はurlってワケ。 (うん、本当に変数がメモリアドレスを持つのかは処理系によるよ。市民、あなたにこのチャプターも不要のようですね)

賢明な読者諸君はこの説明で冒頭のコードが何故こんな挙動になるのか理解出来たかもしれない。そう、変数catが持っているのは文字列"にゃーん"が格納されているメモリ上のアドレスであり、変数animalに代入されたのもcatが持っていた文字列"にゃーん"へのアドレスだからだ。つまり、catとanimalはメモリ上の領域を共有しているってワケ。

実際に見てみよう

cat = 'にゃーん'
animal = cat
animal = 'わんわん'
puts cat
# => "にゃーん"

では何故こんなコードは影響を受けないのかって? animal = 'わんわん'が処理された時に起こるのはメモリ上のデータ操作ではなくて変数animalに文字列"わんわん"が格納されている場所への新しいアドレスを持たせる、だからだよ。さっきのWebページとurlの例えで言えば、冒頭のコードはWebページを書き換えていて今度のコードはurlを書き換えるに過ぎないってワケ。

その違いがどこから生まれるかといえば、オブジェクトが新しく作られるのかどうかによるんだ。新しいオブジェクトが代入されるのなら違うメモリ領域が確保されるよ。例え代入されるオブジェクトの内容が全く同じでもね

# まったく同じ内容のオブジェクトでもメモリ領域は別に確保される
puts 'にゃーん'.object_id
puts 'にゃーん'.object_id
# =>
# 70195628982040
# 70195628981940

# 実のところ文字列リテラルによる記述もStringクラスのnewメソッドを使ってインスタンスを作るのと同じだ
# 'にゃーん' => String.new('にゃーん')

# FixnumやSymbol、Nilなど一部のオブジェクトは例外
puts 1.object_id
puts 1.object_id
# => 
# 3
# 3

# 複数の変数が同じオブジェクトへの参照を持つことも出来る
cat = 'にゃーん'
animal = cat

puts cat.object_id
puts animal.object_id
# => 
# 70293867822040
# 70293867822040

object_idメソッドはオブジェクトがそれぞれ持つ固有のIDだ。本当は違うけど今回説明しているメモリのアドレスみたいなものだと思ってほしい。

1行目2行目のStringオブジェクトが違うIDを持っているのがわかるだろう。一方でFixnumのようなオブジェクトは何度試しても同じIDだ。これは当然のことでStringのようなオブジェクトは冒頭コードのようにslice!メソッドなど破壊的なメソッドによって変更できる(mutable)からだよ。

でもFixnumオブジェクトなど一部のオブジェクトは常に不変で変わることがないんだ。もし、そんなことが可能だったらすごく困ったことになるからね。

# 上で見た通り、この二つの変数は同一のオブジェクトと紐づいている
cat_price = 100
dog_price = 100

cat_price.increment! # 破壊的に1加算するFixnumの架空メソッド
puts cat_price
puts dog_price
# =>
# 101
# 101

えぇ? でもcat_price += 1のような処理は普通に出来るだろうって? +=はメモリ上のデータを更新しているわけじゃない。これは再代入だ。つまり、cat_price = cat_price + 1と同義ってワケ。

<< 前のチャプターへ戻る | 次のチャプターへ進む >>