Go by Example

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