Structs
Struct definition, field access, struct literals, anonymous structs, embedding, and promoted fields.
A struct groups related fields under one name. Structs are the primary way to define domain types in Go - Go has no classes, but structs with methods give you all the encapsulation you need.
Define a struct with type Name struct { ... }. Fields have a name and a type. Access fields with .. The zero value of a struct is a struct with all fields zeroed - no constructor required.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// Struct literal - positional (fragile, avoid in production)
p1 := Person{"Alice", 30}
// Named fields - order-independent, recommended
p2 := Person{Name: "Bob", Age: 25}
// Zero value - all fields are zeroed
var p3 Person
fmt.Println(p1, p2, p3)
fmt.Println(p2.Name, p2.Age) // Bob 25
p2.Age = 26 // field mutation
fmt.Println(p2.Age) // 26
}An anonymous struct has no named type. Use them for one-off groupings - test table entries, local temporary aggregations - where defining a named type would be noise.
package main
import "fmt"
func main() {
point := struct {
X, Y int
}{X: 3, Y: 7}
fmt.Println(point.X, point.Y) // 3 7
// Common in table-driven tests:
cases := []struct {
input int
expected int
}{
{1, 2},
{5, 10},
}
for _, c := range cases {
fmt.Println(c.input, "->", c.expected)
}
}Embedding includes one struct type inside another without a field name. The outer struct promotes the embedded type's fields and methods - they are accessible directly. This is composition, not inheritance.
package main
import "fmt"
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct {
Animal // embedded - Dog promotes Name and Speak
Breed string
}
func main() {
d := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Labrador",
}
// Promoted fields and methods - accessed directly on Dog
fmt.Println(d.Name) // Rex (promoted from Animal)
fmt.Println(d.Speak()) // Rex makes a sound (promoted from Animal)
fmt.Println(d.Breed) // Labrador
}In production
Struct embedding promotes methods but the embedded type is still the receiver - the promoted method runs on the embedded value, not on the outer struct. If Dog tries to override Speak by defining its own Speak method, the outer method shadows the promoted one - but there is no polymorphic dispatch the way inheritance provides. Teams that treat embedding as inheritance get surprised when promoted methods bypass overrides, especially when the promoted method holds a reference to the embedded type and calls back into it. The outbox pattern is a case where embedded structs appear in event payloads - the field ordering and zero-value behaviour of embedded types must be understood before serialising them.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free