kg-icon
Go言語100Tips - 4章
2025-10-02

rangeループについて

Goにはrangeというものがあり、以下のようなデータ構造に対し反復処理を行えます。

  • 文字列
  • 配列
  • 配列に対するポインタ
  • スライス
  • マップ
  • 受信チャネル

Typecriptでいうところのfor...ofに近いかなと思います。

rangeは、上記の型ごとに最適化された形で値を取り出してくれるのでとても便利です。インデックスと値を同時に取り出せたりもする(受信チャネルを除く)ので非常によく使いますね。

ただ注意点もあって、rangeが代入するものは全てコピーとなります。

例えばこんなコードがあったとして

go
1package main
2
3import "fmt"
4
5type User struct {
6	name  string
7	email string
8}
9
10func main() {
11	users := []User{
12		{
13			name:  "A",
14			email: "[email protected]",
15		},
16		{
17			name:  "B",
18			email: "[email protected]",
19		},
20		{
21			name:  "C",
22			email: "[email protected]",
23		},
24	}
25	for _, u := range users {
26		u.name = "new " + u.name
27		u.email = "new " + u.email
28	}
29	fmt.Println(users)
30}

出力は以下のようになります。

こんな感じにはなりません。

text
1[{new A new [email protected]} {new B new [email protected]} {new C new [email protected]}]

なぜか?それは、rangeに代入されるものは全てコピーされるからです。

コピーされると言うことは元の構造体には影響を与えないということですね。

なのでスライスを更新したい場合は、こんな感じでスライスにインデックスを指定して要素にアクセスすればいいです。

go
1for i, u := range users {
2		users[i].name = "new " + u.name
3		users[i].email = "new " + u.email
4	}
5	fmt.Println(users)
text
1[{new A new [email protected]} {new B new [email protected]} {new C new [email protected]}]

rangeにおける引数の評価方法について

こんなコードがあります。

go
1package main
2
3func main() {
4	nums := []int{
5		1, 2, 3, 4, 5,
6	}
7	for i, _ := range nums {
8		nums = append(nums, i)
9	}
10}

rangeの中でnumsにインデックスをどんどんappendしています。

このループはぱっと見終わらない感じがしますが、このループは正常に終了します。

なぜか?

それはnumsが代入される時は1回しか評価されないからです。

評価されるとはつまり、与えられたnumsが一時変数にコピーされるのは1回だけということです。

なので、以降numsにappendしてもそれは一時変数なので問題なくループが終了するわけですね。

しかしこれは終わりません。

go
1package main
2
3func main() {
4	nums := []int{
5		1, 2, 3, 4, 5,
6	}
7	for i := 0; i < len(nums); i++ {
8		nums = append(nums, i)
9	}
10}

len(nums)はループが終わるたびに評価されるからです。

これを区別しておかないと意図しないループをしてしまう可能性があります。