これは、なにをしたくて書いたもの?
Goの構造体の初期化方法はいろいろありますが、どれがどう違うんだっけ?というのをよく忘れるのでメモしておきます。
環境
今回の環境は、こちらです。
$ go version go version go1.16 linux/amd64
確認用のモジュールの作成。
$ go mod init struct-initialize go: creating new go.mod: module struct-initialize
確認には、テストコードを使いましょう。
$ go get github.com/stretchr/testify
go.mod
module struct-initialize go 1.16 require github.com/stretchr/testify v1.7.0 // indirect
仕様
まずは、構造体に関する言語仕様を見てみます。
The Go Programming Language Specification / Struct types
こちらには、初期化に関することは書いていないようです。
組み込み関数である、new
に関する記述をちょっと見てみましょう。この関数を使うと、初期化したうえでポインタが返ります。
The built-in function new takes a type T, allocates storage for a variable of that type at run time, and returns a value of type *T pointing to it. The variable is initialized as described in the section on initial values.
The Go Programming Language Specification / Allocation
こちらから、初期化に関する記述にリンクがありました。
so for instance each element of an array of structs will have its fields zeroed if no value is specified.
The Go Programming Language Specification / The zero value
まあ、各フィールドが"ゼロ"として初期化されることが書かれているわけですが。
他にも{}
を使ったりといった方法があるはずですが。
こちらでしょうか。Composite literalと呼ぶみたいです。
Composite literals construct values for structs, arrays, slices, and maps and create a new value each time they are evaluated.
The Go Programming Language Specification / Composite literals
さらにここに&
を使うと、リテラルで構造体を初期化したうえでポインタを取得する、ということになるみたいですね。
Taking the address of a composite literal generates a pointer to a unique variable initialized with the literal's value.
だいたいわかった気がします。仕様を押さえるのはこのくらいにして、あとは実際の動きを見てみましょう。
確認する
こんな感じで、テストコードの雛形を用意。
main_test.go
package main import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) // ここにテストなどを書く
確認対象の構造体を用意します。
type Person struct { FirstName string LastName string Age int }
では、確認していきましょう。
構造体をvar
で宣言。The zero valueにあたりますね。
func TestDeclareVar(t *testing.T) { var p Person assert.Equal(t, "main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) }
Composite literalsを使った場合。フィールドは指定しません。
func TestCompositeLiteral(t *testing.T) { p := Person{} assert.Equal(t, "main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) }
こうすると、var
で宣言しただけと同じですね。
Composite literalsを使い、フィールドまで指定した場合。
func TestCompositeLiteralFields(t *testing.T) { p := Person{FirstName: "カツオ", LastName: "磯野", Age: 11} assert.Equal(t, "main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "{FirstName:カツオ LastName:磯野 Age:11}", fmt.Sprintf("%+v", p)) }
ポインタ型で宣言した場合。
func TestDeclarePointerVar(t *testing.T) { var p *Person assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "<nil>", fmt.Sprintf("%+v", p)) }
この場合、型はポインタ型になるわけですが、値はnil
になります。
これは、The zero valueでポインタ型はnil
になると書かれているからですね。
nil for pointers
new
関数を使った場合。Allocationですね。
func TestPointerNew(t *testing.T) { p := new(Person) assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "&{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) }
Composite Literalを使い、かつフィールドを指定していない場合と差がありませんね。
Composite literalsを使い、ポインタを取得する場合。フィールドは指定しません。
func TestCompositeLiteralTakePointer(t *testing.T) { p := &Person{} assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "&{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) }
最後は、Composite literalsを使ってポインタを取得しつつ、フィールドも指定する場合。
func TestCompositeLiteralFieldsTakePointer(t *testing.T) { p := &Person{FirstName: "カツオ", LastName: "磯野", Age: 11} assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "&{FirstName:カツオ LastName:磯野 Age:11}", fmt.Sprintf("%+v", p)) }
こんな感じでしょうか。
Composite Literalを使った場合に、指定しなかったフィールドがどのような初期値を持つかは、やっぱりThe zero valueを
見ましょう。
false for booleans, 0 for numeric types, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps.
オマケ
今回作成したソースコード全体も載せておきます。
main_test.go
package main import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) type Person struct { FirstName string LastName string Age int } func TestDeclareVar(t *testing.T) { var p Person assert.Equal(t, "main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) } func TestCompositeLiteral(t *testing.T) { p := Person{} assert.Equal(t, "main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) } func TestCompositeLiteralFields(t *testing.T) { p := Person{FirstName: "カツオ", LastName: "磯野", Age: 11} assert.Equal(t, "main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "{FirstName:カツオ LastName:磯野 Age:11}", fmt.Sprintf("%+v", p)) } func TestDeclarePointerVar(t *testing.T) { var p *Person assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "<nil>", fmt.Sprintf("%+v", p)) } func TestPointerNew(t *testing.T) { p := new(Person) assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "&{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) } func TestCompositeLiteralTakePointer(t *testing.T) { p := &Person{} assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "&{FirstName: LastName: Age:0}", fmt.Sprintf("%+v", p)) } func TestCompositeLiteralFieldsTakePointer(t *testing.T) { p := &Person{FirstName: "カツオ", LastName: "磯野", Age: 11} assert.Equal(t, "*main.Person", fmt.Sprintf("%T", p)) assert.Equal(t, "&{FirstName:カツオ LastName:磯野 Age:11}", fmt.Sprintf("%+v", p)) }