GolangとRubyにおけるループ変数とメモリアドレスの関係
プログラミング言語においてループは非常に一般的な構造で、データの集合に対して繰り返し操作を行う際に使用されます。
しかし、異なるプログラミング言語では、ループ内の変数のスコープやメモリアドレスの扱い方が異なるため、予期しないバグが発生することがあります。
ここでは、特にGolangとRubyのループ変数の挙動の違いに焦点を当てます。
Golangのループ変数
Golangでは、forループを使用する際、ループ変数(例えばiやvalue)はループの各イテレーションで同じメモリアドレスを再利用します。この挙動は、ループ変数のアドレスをポインタとして他の変数に保存し、ループ外で使用する場合に問題を引き起こす可能性があります。以下の例を見てみましょう。
期待通りに動かない例
var pointers []*int
for i := 0; i < 3; i++ {
pointers = append(pointers, &i)
}
for _, ptr := range pointers {
fmt.Println(*ptr) // 出力は期待する 0, 1, 2 ではなく、すべてが 3 になる
}
このコードは、すべてのポインタがiの最終値を指すため、予期しない出力が得られます。この問題を回避するためには、各イテレーションで新しい変数を宣言し、そのアドレスを使用する必要があります。
期待通りに動く例
var pointers []*int
for i := 0; i < 3; i++ {
iCopy := i // i の値のコピーを作成
pointers = append(pointers, &iCopy) // コピーのアドレスをリストに追加
}
for _, ptr := range pointers {
fmt.Println(*ptr) // 期待通りの出力: 0, 1, 2
}
Rubyのループ変数
一方、Rubyでは各ループイテレーションで新しいスコープが作成されるため、ブロック内の変数はそれぞれ独立しています。しかし、Rubyもオブジェクト参照を基に動作するため、ミュータブルなオブジェクト(配列やハッシュなど)の操作では注意が必要です。以下のRubyコードを考えてみましょう。
期待通りに動かない例
オブジェクトをループ外で宣言し、その値を変更する場合、全ての参照が最終的な変更を反映します。
items = []
item = {}
3.times do |i|
item[:id] = i
items << item
end
items.each { |item| puts item[:id] } # すべての出力が 2 になる
このコードでは、itemは同じオブジェクトの参照を三回配列に追加しているため、すべての要素が最後に設定された値を持ちます。各ループの item は同じメモリアドレスにあります。
期待通りに動く例
items = []
3.times do |i|
items << { id: i }
end
items.each { |item| puts item[:id] } # 期待通り 0, 1, 2 が出力される
この場合、各イテレーションで新しいハッシュが生成されるため、問題は発生しません。メモリアドレスも独立しています。
まとめ
GolangとRubyではループ変数のスコープとメモリアドレスの扱い方が根本的に異なります。Golangではポインタの挙動に注意が必要であり、Rubyではオブジェクトの参照とミュータビリティに注意を払う必要があります。各言語の特性を理解し、それに合わせたコーディングスタイルを採用することで、バグを回避しやすくなります。
(Java,Rust も追加予定)
Discussion