author
Kevin Kelche

Golang Structs - A Deep Dive


Introduction

In Go, a struct is a fundamental building block of the language as it is used to represent a collection of data. Struct fields can be of any type, including other structs, and can have methods. In this article, we explore details of how to use them in your Go programs.

Defining a Struct

Structs are defined using the type keyword and the struct keyword.

type <StructName> struct {
  <FieldName> <FieldType>
  <FieldName> <FieldType>
  ...
}

Copied!

Let’s define a struct that represents a person.

type Person struct {
  FirstName string
  LastName string
  Age int
}

Copied!

Initializing a Struct

Initializing an Empty Struct

Structs can be created in two ways. The first way is to use the new keyword.

p := new(Person)

Copied!

The second way is to use the & operator.

p := &Person{}

Copied!

Both of these methods will create a pointer to a struct. The new keyword is a built-in function that allocates memory for a struct and returns a pointer to it. The & operator is a shorthand for creating a pointer to a struct. Learn more about new here

Initializing a Struct with Values

To initialize a struct with values, we can use the & operator.

p := &Person{
  FirstName: "Russ",
  LastName: "Blinken",
  Age: 30,
}

Copied!

We can also initialize without using the & operator.

p := Person{
  FirstName: "Russ",
  LastName: "Blinken",
  Age: 30,
}

Copied!

Accessing Struct Fields

Struct fields are accessed using the . operator.

p := &Person{
  FirstName: "Russ",
  LastName: "Blinken",
  Age: 30,
}

fmt.Println(p.FirstName) // Russ
fmt.Println(p.LastName) // Blinken
fmt.Println(p.Age) // 30

Copied!

Since p is a pointer to a struct, we can also use the pointer dereference operator * to access the fields.

fmt.Println((*p).FirstName) // Russ
fmt.Println((*p).LastName) // Blinken
fmt.Println((*p).Age) // 30

Copied!

This will only work when you use the & operator or new keyword to initialize the struct.

Struct Methods

Structs can have methods defined on them. These methods are similar to functions, but are defined on a struct and are used to encapsulate any logic related to the struct.

type Person struct {
  FirstName string
  LastName string
  Age int
}

func (p Person) fullName() string {
  return p.FirstName + " " + p.LastName
}

Copied!

The fullName method is defined on the Person struct and can access the fields of the the struct using the p parameter. The fullName method can then be called on a Person struct.

p := &Person{
  FirstName: "Russ",
  LastName: "Blinken",
  Age: 30,
}

fmt.Println(p.fullName()) // Russ Blinken

Copied!

Pointer Receivers vs Value Receivers

When defining a method on a struct, we can use either a pointer receiver or a value receiver.

Pointer Receiver - Mutates the struct

A pointer receiver is defined by using the * operator in the method definition. Pointer receivers are useful when we want to modify the fields of the struct.

func (p *Person) incrementAge() {
  p.Age++
}

Copied!

Value Receiver - Does not mutate the struct

On the other hand, a value receiver is defined by using the struct name only in the method definition. They are useful when we want to create a copy of the struct and modify the copy or just read the fields of the struct.

func (p Person) incrementAge() {
    fmt.Println(p.Age) // 30
    p.Age++
    fmt.Println(p.Age) // 31
}

Copied!

The p.Age++ line will not modify the Age field of the struct. If you try to access the Age field of the struct outside of the method, you will see that it has not been modified.

p.incrementAge() // 30, 31
fmt.Println(p.Age) // 30

Copied!

Struct Embedding

Structs like interfaces can be embedded into other structs. This is useful when we want to reuse the fields and methods of a struct in another struct. This introduces the concept of composition in Go.

type Person struct {
  FirstName string
  LastName string
  Age int
}

type Employee struct {
  Person
  Salary int
}

Copied!

In this example, the Employee struct embeds the Person struct. Meaning that the Employee struct has access to the fields and methods of the Person struct.

emp1 := &Employee{
    Person: Person{
        FirstName: "Russ",
        LastName: "Blinken",
        Age: 30,
    },
    Salary: 100000,
}

Copied!

Since the Employee struct embeds the Person struct, we can access the fields of the Person struct using the . operator.

fmt.Println(emp1.FirstName) // Russ
fmt.Println(emp1.Salary) // 100000

Copied!

We can also access the fields of the Person struct using the Person field.

fmt.Println(emp1.Person.FirstName) // Russ

Copied!

Struct Tags

Struct tags are metadata that can be attached to struct fields to provide additional information about the field. Struct tags are defined using backticks.

type Person struct {
  FirstName string `required:"true" max:"100"`
  LastName string
  Age int
}

Copied!

Struct tags can be accessed using the reflect package.

t := reflect.TypeOf(Person{})
field, _ := t.FieldByName("FirstName")

fmt.Println(field.Tag) // required:"true" max:"100"

Copied!

Struct tags are commonly used with JSON encoding and decoding as they provide additional information about the fields of a struct. Learn more about JSON encoding and decoding in Go here

Conclusion

In this article, we learned about structs in Go. Here are some key takeaways:

Subscribe to my newsletter

Get the latest posts delivered right to your inbox.