Nested Structs
Go allows us to use a struct as a field of another struct, this pattern is called nesting. A nested struct can be defined using the following syntax.
type struct1 struct{
// fields
}
type struct2 struct{
// fields
s struct1
}
Suppose we need to collect the data of a person, the person's name, age and address. In the address, we need to collect the city and country of that person. So we can do this using nested structs as shown below:
package main
import "fmt"
type Person struct {
name string
age int
address Address
}
type Address struct {
city, country string
}
func main() {
person := &Person{
name: "James Bond",
age: 34,
address: Address{
city: "London",
country: "UK",
},
}
fmt.Printf("%#v", person)
}
&main.Person{name:"James Bond", age:34, address:main.Address{city:"London", country:"UK"}}
Run this code in Go Playground
Accessing fields of nested struct
To access the fields of nested structs we can do structObj1.structObj2.structObj3........fieldName
.
Below is an example:
package main
import "fmt"
type Person struct {
name string
age int
address Address
}
type Address struct {
city, country string
}
func main() {
person := &Person{
name: "James Bond",
age: 34,
address: Address{
city: "London",
country: "UK",
},
}
fmt.Printf("Name: %v\n", person.name)
fmt.Printf("Age: %v\n", person.age)
fmt.Printf("City: %v\n", person.address.city)
fmt.Printf("Country: %v\n", person.address.country)
}
Name: James Bond
Age: 34
City: London
Country: UK
Run this code in Go Playground
Promoted fields
When the struct has another struct as an anonymous field then fields of anonymous structs are called promoted fields. This means we can access the fields of the nested struct without using the object of the nested struct, we can access them just by using the parent struct.
For example:
package main
import "fmt"
type Person struct {
name string
age int
Address
}
type Address struct {
city, country string
}
func main() {
person := &Person{
name: "James Bond",
age: 34,
Address: Address{
city: "London",
country: "UK",
},
}
fmt.Printf("Name: %v\n", person.name)
fmt.Printf("Age: %v\n", person.age)
fmt.Printf("City: %v\n", person.city) // Instead of person.address.city we used person.city
fmt.Printf("Country: %v\n", person.country) // Instead of person.address.country we used person.country
}
Name: James Bond
Age: 34
City: London
Country: UK
Run this code in Go Playground
Only unique fields of nested anonymous struct gets promoted.
If the parent struct and nested anonymous struct have the same name field then that field will not be promoted.
package main
import "fmt"
type Person struct {
name string
age int
City
}
type City struct {
name, country string
}
func main() {
person := &Person{
name: "James Bond",
age: 34,
City: City{
name: "London",
country: "UK",
},
}
fmt.Printf("Name: %v\n", person.name)
fmt.Printf("Age: %v\n", person.age)
fmt.Printf("City: %v\n", person.City.name)
fmt.Printf("Country: %v\n", person.country)
}
Run this code in Go Playground
In this example, we have City
a nested anonymous struct, whose fields are name
and country
. The name
the field is also available in the Person
struct. So when we try person.name
then the compiler will give us access to the Person's name field by default hence to access the name field from the City struct we've to do person.City.name
. The country field from City struct is unique here so we can just access it by using person.country
.
Comparing struct
Structs in Go are value type and so they can be compared.
Two struct values may be equal if:
They're of the same type.
Their corresponding fields are equal.
Their corresponding fields should be comparable.
For example:
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
p1 := Person{
name: "James Bond",
age: 34,
}
p2 := Person{
name: "James Bond",
age: 34,
}
fmt.Println(p1 == p2) // true
}
Run this code in Go Playground
Comparing pointers to the struct will always be false, we need to compare dereferenced pointer values.
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
p1 := &Person{
name: "James Bond",
age: 34,
}
p2 := &Person{
name: "James Bond",
age: 34,
}
fmt.Println(p1 == p2) // false
fmt.Println(*p1 == *p2) // true
}
Run this code in Go Playground
If structs contain incomparable values then struct values can not be compared using ==
operator, it will throw an error.
package main
import "fmt"
type Person struct {
name string
age int
measures map[string]float32
}
func main() {
p1 := Person{
name: "James Bond",
age: 34,
measures: map[string]float32{
"Height": 183,
"Weight": 76,
},
}
p2 := Person{
name: "James Bond",
age: 34,
measures: map[string]float32{
"Height": 183,
"Weight": 76,
},
}
fmt.Println(p1 == p2)
}
./prog.go:30:18: invalid operation: p1 == p2 (struct containing map[string]float32 cannot be compared)
In the above code, a map is used as a field in a struct and a map is an incomparable type. To compare such structs we can use reflect.DeepEqual()
function.
package main
import (
"fmt"
"reflect"
)
type Person struct {
name string
age int
measures map[string]float32
}
func main() {
p1 := Person{
name: "James Bond",
age: 34,
measures: map[string]float32{
"Height": 183,
"Weight": 76,
},
}
p2 := Person{
name: "James Bond",
age: 34,
measures: map[string]float32{
"Height": 183,
"Weight": 76,
},
}
fmt.Println(reflect.DeepEqual(p1, p2)) // true
}
Run this code in Go Playground
Structs and methods/receiver function
Golang supports both function and method. A method is a function that is defined for a particular type or with a receiver. A method in Golang is also called a receiver function. Following is the example.
package main
import "fmt"
type Person struct {
name string
age int
}
func (p Person) GetInfo() string {
return fmt.Sprintf("\"%v\" is \"%v\" years old.", p.name, p.age)
}
func main() {
p := Person{
name: "James Bond",
age: 34,
}
fmt.Println(p.GetInfo())
}
"James Bond" is "34" years old.
Run this code in Go Playground
We can also use a pointer as the receiver of the method. The difference between value as receiver and pointer as a receiver is, golang passes everything as value and hence in value receiver the function will get a copy of the struct and function will not touch the original struct whereas in pointer as a receiver function will get a copy of a pointer which will be pointing to an original struct.
The below examples illustrate the difference between a pointer and a value as a receiver.
package main
import "fmt"
type Person struct {
name string
age int
}
// Value as receiver
func (p Person) UpdateAge(age int) {
p.age = age
}
// Pointer as receiver
func (p *Person) UpdateName(name string) {
p.name = name
}
func main() {
p := Person{
name: "James Bond",
age: 34,
}
// Value as receiver example
fmt.Println("Age before update: ", p.age)
p.UpdateAge(24)
fmt.Println("Age after update: ", p.age)
fmt.Println()
// Pointer as receiver
fmt.Println("Name before update: ", p.name)
p.UpdateName("Jon Snow")
fmt.Println("Name after update: ", p.name)
}
In above example UpdateAge()
is using value as the receiver and UpdateName()
is using pointer as a receiver.
Age before update: 34
Age after update: 34
Name before update: James Bond
Name after update: Jon Snow
Run this code in Go Playground
Empty Struct
In Golang, we can have an empty struct. A struct without any field is called an empty struct. Below is the signature of an empty struct.
type T struct{}
var s struct{}
Empty struct does not have any field so it does not occupy any memory i.e it occupies zero bytes
of storage.
var s struct{}
fmt.Println(unsafe.Sizeof(s)) // prints 0
We can create an array of the empty struct and still it occupies zero bytes.
var arr [100000]struct{}
fmt.Println(unsafe.Sizeof(arr)) // prints 0
The slice of structs will consume some bytes just to store the pointer, length and capacity of an underlying array but structs still will be of zero bytes.
sls := make([]struct{}, 100000)
fmt.Println(unsafe.Sizeof(sls)) // prints 24
package main
import (
"fmt"
"unsafe"
)
func main() {
var s struct{}
fmt.Println("Size of an empty struct: ", unsafe.Sizeof(s)) // prints 0
var arr [100000]struct{}
fmt.Println("Size of an array of an empty struct: ", unsafe.Sizeof(arr)) // prints 0
sls := make([]struct{}, 100000)
fmt.Println("Size of a slice of an empty struct: ", unsafe.Sizeof(sls)) // prints 24
}
Size of an empty struct: 0
Size of an array of an empty struct: 0
Size of a slice of an empty struct: 24
Run this code in Go Playground
Read more about the empty struct and its use cases at Dave Cheney's blog.
Array/Slice of structs
The Array/Slice is a collection of the same type of element in a contiguous memory location. We can create an array/slice of structs.
package main
import "fmt"
type Person struct {
name string
age int
}
func newPerson(name string, age int) *Person {
return &Person{
name: name,
age: age,
}
}
func main() {
people := make([]*Person, 0) // slice of a struct Person with initial length 0
people = append(people, newPerson("James Bond", 34))
people = append(people, newPerson("Jon Snow", 24))
people = append(people, newPerson("Joey Tribbiani", 32))
for _, person := range people {
fmt.Println(person)
}
fmt.Println("Name of friends character: ", people[2].name)
}
&{James Bond 34}
&{Jon Snow 24}
&{Joey Tribbiani 32}
Name of friends character: Joey Tribbiani
Run this code in Go Playground
Thank you for reading this blog, and please give your feedback in the comment section below.