21
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Goのfor rangeでポインタを使用する際に気をつけたいこと

Last updated at Posted at 2017-12-23

該当ケース

ある構造体配列をポインタの参照渡しで別の構造体配列にコンバートする際に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 =  男
--

最後に

簡単なことですが、ポインタの扱いに慣れていないと今回のようなことが起きるので参照先には
注意が必要ですね。
似たような実装でハマった方はぜひご参考にして頂ければ幸いです。

21
18
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?