Structs in Go
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.
