Structs in Go

The Go programming language is not an object-orientated language. There are no Class objects, or inheritance, that you typically find in other OOP languages like Python, Java, C#, and others. However, there is an alternative data type in Go called struct, short for structure, that provides us with comparable features.

In this post, we introduce the struct data type in Go and how to create and use them.

Create a Struct

Creating a struct is done with the type <struct name> struct {} syntax.

In the example below, we create an IceCream struct with field values for Flavor, Description, Price, and Vegan. After each field, we specify the field type.

package main

import "fmt"

type IceCream struct {
	Flavor      string
	Description string
	Price       float64
	Vegan       bool
}

func main() {
	icecream := IceCream{
		Flavor:      "Vanilla",
		Description: "Plain tasting ice cream",
		Price:       5.99,
		Vegan:       false,
	}
	// print the struct
	fmt.Println(icecream)

	// print a single field value
	fmt.Println(icecream.Flavor)
}

The output is:

{Vanilla Plain tasting ice cream 5.99 false}
Vanilla

The Go compiler lets you create structs without using the field names too. The code snippet below creates the same struct but without the use of field names.

icecream := IceCream{
	"Vanilla",
	"Plain tasting ice cream",
	5.99,
	false,
}

However, this reduces clarity and could make it more difficult to create maintainable code in larger production software. Many Go programmers prefer the explicit declaration method of <field name>: <field value>.

Change Field Values

Structs are mutable. We can assign new field values to a struct after it’s already been created.

icecream.Price = 10.99
icecream.Price += 1

Zero Value by Default

When you create a struct without specifying all the field values, the zero-value gets assigned to those fields.

For example, the code snippet below creates an icecream struct with only the Flavor and Vegan fields defined. As a result, the Price field is set to 0 and the Description field is set to an empty string.

icecream := IceCream{
	Flavor: "Vanilla",
	Vegan:  false,
}
// print the price
fmt.Println(icecream.Price)
// print the description
fmt.Println(icecream.Description)

The output is:

0

Go provides several printing “verbs” that can be used to format the output of printed values. These can be found in the Go documentation for the fmt package.

Update the previous example with a format print statement. This will print both the field names and field values.

fmt.Printf("%+v\n", icecream)

The output is:

{Flavor:Vanilla Description:Plain tasting ice cream Price:10.99 Vegan:false}

More on Structs

Structs can be associated, or nested, with other structs. One struct might also contain a slice of other structs. This is something we’ll try next.

Let’s create a new struct that contains a list of IceCream structs. This is a common way to collect and organize data.

Below is a basic example on how to achieve this in Go.

package main

import "fmt"

type IceCream struct {
	Flavor   string
	Price    float64
	Quantity int
}

type Inventory struct {
	Items []IceCream
}

func main() {
	inventory := Inventory{}
	icecream := IceCream {
		Flavor:   "Vanilla",
		Price:    5.99,
		Quantity: 10,
	}
	inventory.Items = append(inventory.Items, icecream)

	icecream2 := IceCream {
		Flavor:   "Chocolate",
		Price:    7.99,
		Quantity: 12,
	}
	inventory.Items = append(inventory.Items, icecream2)

	// print the inventory
	fmt.Println(inventory)
}

An IceCream struct is defined with three fields representing the Flavor, Price, and Quantity. We then create an Inventory struct with a single field Items that contains a slice of IceCream structs.

In the main() function, we create a variable inventory to be of type Inventory{}. Then two IceCream structs are created and appended to the inventory.

Finally, we print our inventory and get the following:

{[{Vanilla 5.99 10} {Chocolate 7.99 12}]}

If we sell 2 pints of Vanilla ice cream, we could use the following code to update our inventory.

inventory.Items[0].Quantity -= 2
fmt.Println(inventory)

Dot-notation is used to reference the first element in the Items slice in the inventory struct, and 2 is subtracted.

The print statement outputs:

{[{Vanilla 5.99 8} {Chocolate 7.99 12}]}

Struct Methods

Just like Classes in other languages, structs can have methods associated with them. However, in Go, they’re defined outside the struct, which seems a little weird if you’re familiar with other languages.

To create a struct method, a function is defined using a receiver value to the struct.

In the example below, we create a value() method with an (i IceCream) receiver value. We can call the method for any IceCream struct we have using icecream.value(). The float64 portion is the return value.

Inside the method, the value i is the equivalent of the this or self keywords used in other languages. It references the struct that the method was called from and lets us access fields within the struct with a . and the field name.

package main

import "fmt"

type IceCream struct {
	Flavor   string
	Price    float64
	Quantity int
}

func (i IceCream) value() float64 {
	return i.Price * float64(i.Quantity)
}

func main() {
	icecream := IceCream{
		Flavor:   "Vanilla",
		Price:    5.99,
		Quantity: 10,
	}

	fmt.Printf("We have $%.2f worth of Vanilla ice cream.", icecream.value())
}

When we call icecream.value() we are taking the icecream struct and passing it into the value() method. The struct’s field values for Price (10) and Quantity (5.99) are multiplied together and returned, resulting in the output:

We have $59.90 worth of Vanilla ice cream.

Summary

In this post, we looked at what structs are in Go and how they’re used. We explored the syntax of creating a struct, how to change field values, and the different ways to print the struct. We also looked at how to define struct methods and how to call them.

In the next post, we learn more about functions Go functions. Check out the Go page to see other Go lessons too.