該当ケース
ある構造体配列をポインタの参照渡しで別の構造体配列にコンバートする際にfor rangeを使用する
実装例
Employee型配列をPEmployee型配列に値をコピーする
sample.go
package main
import "fmt"
type Employee struct{
ID int
Name string
Age int
Sex string
}
type PEmployee struct{
ID *int
Name *string
Age *int
Sex *string
}
func convertPEmployee(employees []Employee) []PEmployee{
var pEmployees []PEmployee
for _,employee := range employees{
pEmployees = append(pEmployees,PEmployee{
ID: &employee.ID,
Name: &employee.Name,
Age: &employee.Age,
Sex: &employee.Sex,
})
}
return pEmployees
}
func main(){
employees := []Employee{
Employee{
ID: 1,
Name: "山田太郎",
Age: 23,
Sex: "男",
},
Employee{
ID: 2,
Name: "山田花子",
Age: 27,
Sex: "女",
},
Employee{
ID: 3,
Name: "田中次郎",
Age: 36,
Sex: "男",
},
}
pEmployees := convertPEmployee(employees)
fmt.Println("==PEmployees==")
for _,pEmployee := range pEmployees{
fmt.Println("ID = ",*pEmployee.ID)
fmt.Println("Name = ",*pEmployee.Name)
fmt.Println("Age = ",*pEmployee.Age)
fmt.Println("Sex = ",*pEmployee.Sex)
fmt.Println("--")
}
}
上記のsample.goを実行すると以下のようになる
ID = 3
Name = 田中次郎
Age = 36
Sex = 男
ID = 3
Name = 田中次郎
Age = 36
Sex = 男
ID = 3
Name = 田中次郎
Age = 36
Sex = 男
解説
結論をいうとfor rangeのrangeの返り値には同じ参照先が使用されている。
上記のsample.goのconvertPEmployee関数を以下のように変更し、参照先を確認する。
sample.go
func convertPEmployee(employees []Employee) []PEmployee{
var pEmployees []PEmployee
for _,employee := range employees{
fmt.Println("==",i+1," loop==")
fmt.Println("&i = ",&i)
fmt.Println("&employee.ID = ",&employee.ID)
fmt.Println("&employee.Name = ",&employee.Name)
fmt.Println("&employee.Age = ",&employee.Age)
fmt.Println("&employee.Sex = ",&employee.Sex)
fmt.Println("&employee = ",employee)
pEmployees = append(pEmployees,PEmployee{
ID: &employee.ID,
Name: &employee.Name,
Age: &employee.Age,
Sex: &employee.Sex,
})
}
return pEmployees
}
###実行結果
== 1 loop==
&i = 0x8201cc288
&employee.ID = 0x8201d6180
&employee.Name = 0x8201d6188
&employee.Age = 0x8201d6198
&employee.Sex = 0x8201d61a0
&employee = {1 山田太郎 23 男}
== 2 loop==
&i = 0x8201cc288
&employee.ID = 0x8201d6180
&employee.Name = 0x8201d6188
&employee.Age = 0x8201d6198
&employee.Sex = 0x8201d61a0
&employee = {2 山田花子 27 女}
== 3 loop==
&i = 0x8201cc288
&employee.ID = 0x8201d6180
&employee.Name = 0x8201d6188
&employee.Age = 0x8201d6198
&employee.Sex = 0x8201d61a0
&employee = {3 田中次郎 36 男}
i,employee共に同じアドレスが毎回使用されている
※値だけを出力させても一見変更されているように見えるので注意が必要。
解決策
別の作業用変数を新規に定義し、一時的に置き換える必要がある。
※employeeCopyにはループ毎に毎回新しいアドレスが割り当てられる
sample.go
func convertPEmployee(employees []Employee) []PEmployee{
var pEmployees []PEmployee
for _,employee := range employees{
employeeCopy := Employee{
ID: employee.ID,
Name: employee.Name,
Age: employee.Age,
Sex: employee.Sex,
}
pEmployees = append(pEmployees,PEmployee{
ID: &employeeCopy.ID,
Name: &employeeCopy.Name,
Age: &employeeCopy.Age,
Sex: &employeeCopy.Sex,
})
}
return pEmployees
}
実行結果
==PEmployees==
ID = 1
Name = 山田太郎
Age = 23
Sex = 男
--
ID = 2
Name = 山田花子
Age = 27
Sex = 女
--
ID = 3
Name = 田中次郎
Age = 36
Sex = 男
--
最後に
簡単なことですが、ポインタの扱いに慣れていないと今回のようなことが起きるので参照先には
注意が必要ですね。
似たような実装でハマった方はぜひご参考にして頂ければ幸いです。