We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

Structs in Go

Lane Wagner
Lane WagnerBoot.dev co-founder and backend engineer

Last published

Table of Contents

Structs are one of Go's core tools for organizing related data.

All the content from our Boot.dev courses are available for free here on the blog. This one is the "Structs" chapter of Learn Go. If you want to try the far more immersive version of the course, do check it out!

What Is a Struct in Go?

We use structs in Go to represent structured data. It's often convenient to group different types of variables together. For example, if we want to represent a car we could do the following:

type car struct {
	brand   string
	model   string
	doors   int
	mileage int
}

This creates a new struct type called car. All cars have a brand, model, doors and mileage.

To create a car, use a struct literal:

myCar := car{
	brand:   "Toyota",
	model:   "Camry",
	doors:   4,
	mileage: 5000,
}

Structs in Go are often used to represent data that you might use a dictionary or object for in other languages.

Fields that haven't been assigned use their type's zero value:

  • int: 0
  • bool: false
  • string: ""

Go also lets you declare an anonymous struct inline, without giving the type a name. In general, prefer named structs. Named structs make it easier to read and understand your code, and they have the nice side-effect of being reusable. I sometimes use anonymous structs when I know I won't ever need to use a struct again. For example, sometimes I'll use one to create the shape of some JSON data in HTTP handlers.

Nested Structs in Go

Structs can be nested to represent more complex entities:

type car struct {
	brand      string
	model      string
	doors      int
	mileage    int
	frontWheel wheel
	backWheel  wheel
}

type wheel struct {
	radius   int
	material string
}

The fields of a struct can be accessed using the dot . operator.

myCar := car{}
myCar.frontWheel.radius = 5

Embedded Structs

Go is not an object-oriented language. However, embedded structs provide a kind of data-only inheritance that can be useful at times. Keep in mind, Go doesn't support classes or inheritance in the complete sense, but embedded structs are a way to elevate and share fields between struct definitions.

type car struct {
	brand string
	model string
}

type truck struct {
	// "car" is embedded, so the definition of a
	// "truck" now also additionally contains all
	// of the fields of the car struct
	car
	bedSize int
}

Embedded vs. Nested

  • Unlike nested structs, an embedded struct's fields are accessed at the top level like normal fields.
  • Like nested structs, you assign the promoted fields with the embedded struct in a composite literal.
lanesTruck := truck{
	bedSize: 10,
	car: car{
		brand: "Toyota",
		model: "Tundra",
	},
}

fmt.Println(lanesTruck.brand) // Toyota
fmt.Println(lanesTruck.model) // Tundra

In the example above, car is an embedded struct within truck. You can see that both brand and model are accessible from the top-level, while the nested equivalent to this object would require you to access these fields via a nested car struct: lanesTruck.car.brand or lanesTruck.car.model.

Struct Methods in Go

While Go is not object-oriented, it does support methods that can be defined on structs. Methods are just functions that have a receiver. A receiver is a special parameter that syntactically goes before the name of the function.

type rect struct {
	width  int
	height int
}

// area has a receiver of (r rect)
// rect is the struct
// r is the placeholder
func (r rect) area() int {
	return r.width * r.height
}

var r = rect{
	width:  5,
	height: 10,
}

fmt.Println(r.area())
// prints 50

A receiver is just a special kind of function parameter. In the example above, the r in (r rect) could just as easily have been rec or even x, y or z. By convention, Go code will often use the first letter of the struct's name.

Receivers are important because they allow us to define interfaces that our structs (and other types) can implement.

Memory Layout

In Go, structs sit in memory in a contiguous block, with fields placed one after another as defined in the struct. For example this struct:

type stats struct {
	Reach    uint16
	NumPosts uint8
	NumLikes uint8
}

Looks like this in memory:

Field Ordering... Matters?

The order of fields in a struct can have a big impact on memory usage. This is the same struct as above, but poorly designed:

type stats struct {
	NumPosts uint8
	Reach    uint16
	NumLikes uint8
}

It looks like this in memory:

Notice that Go has "aligned" the fields, meaning that it has added some padding (wasted space) to make up for the size difference between the uint16 and uint8 types. It's done for execution speed, but it can lead to increased memory usage.

Should I Panic?

To be honest, you should not stress about memory layout. However, if you have a specific reason to be concerned about memory usage, aligning the fields by size (largest to smallest) can help. You can also use the reflect package to debug the memory layout of a struct:

typ := reflect.TypeOf(stats{})
fmt.Printf("Struct is %d bytes\n", typ.Size())

Real Story

I once had a server in production that held a lot of structs in memory. Like hundreds of thousands in a list. When I re-ordered the fields in the struct, the memory usage of the program dropped by over 2 gigabytes! It was a huge performance win.

Empty Structs

Empty structs are used in Go as a unary value.

// anonymous empty struct type
empty := struct{}{}

// named empty struct type
type emptyStruct struct{}
empty := emptyStruct{}

The cool thing about empty structs is that they're the smallest possible type in Go: they take up zero bytes of memory.

Empty structs are used surprisingly often, mostly with maps and channels.

Frequently Asked Questions

What is a struct in Go?

A struct groups different types of variables together to represent structured data, such as a car with brand, model, doors, and mileage fields.

How do you create a struct value in Go?

Define a struct type, then use a struct literal to create a value and assign its fields. Fields that are not assigned use their type's zero value.

What is the difference between nested and embedded structs in Go?

A nested struct's fields are accessed through its field name. An embedded struct's fields are promoted, so they can be accessed at the top level like normal fields.

Can structs have methods in Go?

Yes. Go supports methods defined on structs. A method is a function with a receiver, which is a special parameter placed before the function name.

How much memory does an empty struct use in Go?

An empty struct takes up zero bytes of memory, making it the smallest possible type in Go.