cover

Initialization of struct in Go

sorcererxw

Those who are new to Golang are often troubled by the struct in Golang, whether it's the initialization method, various data types, or the combination of structs, all of which are quite different from other languages they have encountered before. This article will sort these out.

Question

There are multiple ways to initialize a struct in Go:

  • Initialization as a value: foo:=Foo{}, you can also create it in the default way var foo Foo
  • Initialization as a pointer: foo:=New(Foo), of course, you can also initialize as a value first and then take the address foo:=&Foo{}
So what are the differences between these methods? Why does Go, which values simplicity, introduce so many different initialization methods?

There are multiple types in Go, including the following:

  • Literal Value/Pointer: Numerical, String, Boolean, Array
  • Struct Value/Pointer
  • slice, map, channel (values that require initialization with make)
  • There is also the combination mode unique to Go struct, which includes the difference between embedded values and embedded pointers.

Define the following Foobar struct:

type Foo struct {
}

func (Foo) callVFoo() {
	fmt.Println("vfoo")
}

func (*Foo) callPFoo() {
	fmt.Println("pfoo")
}

type Bar struct {
}

func (Bar) callVBar() {
	fmt.Println("vbar")
}

func (*Bar) callPBar() {
	fmt.Println("pbar")
}
type Foobar struct {
	Foo
	*Bar

	vFoo Foo
	pFoo *Foo

	vNum int
	pNum *int

	vArr [5]int
	pArr *[5]int

  s  []int
	m  map[int]int
	c  chan int
}
How will these member variables be initialized?

Experiment

Run the following code for different fb and observe the results:

1. fb := Foobar{}
2. fb := new(Foobar)
3. var fb Foobar
4. var fb *Foobar

func test(fb) {
	fmt.Printf("%+v\n", fb)
  fmt.Printf("slice is nil: %t\n", fb.s == nil)
	fmt.Printf("map is nil: %t\n", fb.m == nil)
	fmt.Printf("chan is nil: %t\n", fb.c == nil)

	fb.callPFoo()
	fb.callVFoo()
	fb.callPBar()
	fb.callVBar()
}
运行结果1:
{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果2:
&{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果3:
{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果4:
<nil>
panic: runtime error: invalid memory address or nil pointer dereference

Conclusion

  • For the first three initialization methods, although you may get a struct value or a struct pointer, the memory of the final value they point to is the same. Only when you directly declare a *struct, it cannot be initialized and will get nil.
  • For embedded structs, if the embedded one is a value, it can be initialized; if the embedded one is a pointer, it cannot be initialized.

    Reason: In the case of embedded values, when initializing the external structure, a whole block of continuous memory will be applied, which can also initialize the internal structure. However, in the case of embedded pointers, this will not happen.

    Additionally, even though the embedded struct is nil, you can still call the func with the receiver as a pointer, but you cannot call the func with the receiver as a value.

  • Types such as slice, map, and channel that require make are not automatically initialized. This is because these three types are essentially reference types, which need to reference a block of memory of unknown size, hence they are not directly initialized.