📦

Rubyにおける変数とその挙動について

2024/06/09に公開

変数とは何か?

一般的にプログラミングを学ぶ際、変数は「値を格納する入れ物」として説明されることが多いです。しかし、この表現は初学者にとって分かりやすくするための単純化されたもので、Rubyのようなプログラミング言語の変数の実態とは少し違います。
実際には、変数は単なる入れ物ではなく、値の格納場所を指し示す「参照」や「ポインタ」として機能します。つまり、変数には値自体が直接格納されるのではなく、値が格納されているメモリ位置への参照が格納されるのです。

参照の理解とその挙動

まずはRubyの= 演算子がどのように動作するかを解説します。

参照とは何か?

まず基本から理解しましょう。Rubyにおける変数は、データそのものではなく、データが格納されているメモリの場所を指す「参照」を保持します。つまり、変数には実際のデータへのポインタが含まれており、そのポインタを通じてデータにアクセスすることができます。

= 演算子の役割

Rubyで = 演算子を使用するとき、実際に行われるのは値のコピーではなく、参照(またはポインタ)のコピーです。この挙動を明確に理解するために、以下の例を詳しく見ていきましょう。

data1 = [1, 2, 3]
data2 = data1

このコードでは、最初に配列 [1, 2, 3]data1 という変数に割り当てます。次に、data2 = data1 という行で data1 の持つ参照を data2 にコピーします。

object_id メソッドの使用

この挙動を視覚的に確認するために、Rubyの object_id メソッドを使用します。このメソッドは、オブジェクトの一意の識別子を返し、メモリ上のそのオブジェクトの位置を間接的に示します。識別子が同じであれば、それは同じオブジェクトを参照していることを意味します。上記のコードにおける object_id の呼び出しを見てみましょう。

data1 = [1, 2, 3]
data2 = data1

puts data1.object_id # 60
puts data2.object_id # 60

ここで、data1data2 が同じ object_id を出力しています。これは、両方の変数が同じ配列オブジェクト(メモリ上の同じ位置に存在する配列)を参照していることを示しています。

代入の影響と参照の動作

変数間で参照が共有されることの具体的な影響を見てみましょう。

data1 = [1, 2, 3]
data2 = data1
data1[0] = 10

p data1 # [10, 2, 3]
p data2 # [10, 2, 3]

この例では、data1 の最初の要素を 10 に変更すると、data2 が参照する配列も変更されていることが分かります。これは、両方の変数が同じ配列オブジェクトを共有しているためです。

しかし、次の例では挙動が異なります。

data1 = [1, 2, 3]
data2 = data1

data1 = [4, 5, 6]
p data1 # [4, 5, 6]
p data2 # [1, 2, 3]

p data1.object_id # 60
p data2.object_id # 80

この場合、data1 に新しい配列 [4, 5, 6] を代入することで、data1 の参照が新しい配列に更新されますが、data2 の参照は変わらず最初の配列を指し続けるため、変更は data2 には影響しません。

即値として扱われる型の特例

Rubyではほとんどのオブジェクトが参照によって扱われますが、整数や浮動小数点数の一部の値、truefalsenil、シンボルなどは即値として扱われます。即値とは、変数に直接値が格納される方式を指します。これは処理効率を向上させるための実装です。以下の例を見てみましょう。

num1 = 10
num2 = num1

puts num1.object_id # 21
puts num2.object_id # 21

ここで、num1num2 は同じ object_id を持ちますが、これは処理効率上の理由から同じ値は同じオブジェクトとして管理されるからです。

同じobject_idをもっていますが、次のような場合、挙動が異なります。

num1 = 10
num2 = num1

num1 += 5
p num1 # 15
p num2 # 10

p num1.object_id # 31
p num2.object_id # 21

この例では、num15 を加えたことで 15 という新しい値を持つ新しいオブジェクトが作成され、num1 の参照が更新されます。
しかし、num2 は変更されずに 10 の値を持つ元のオブジェクトを指し続けます。これは整数型がイミュータブル(変更不可能)であるため、値が変更されると新しいオブジェクトが生成されるからです。

参考

独習Ruby

Discussion