Learn Go
beginner1 min read

Structs

A struct is a composite type that groups named fields together. Go does not have classes — structs and their methods are the foundation of Go's object-oriented programming style.

Defining and Creating Structs

package main
 
import "fmt"
 
type Point struct {
    X float64
    Y float64
}
 
func main() {
    // Named fields (preferred — order-independent)
    p1 := Point{X: 3.0, Y: 4.0}
 
    // Positional (fragile — avoid for types with many fields)
    p2 := Point{1.0, 2.0}
 
    // Zero value
    var p3 Point // {0 0}
 
    fmt.Println(p1, p2, p3)
}

Accessing Fields

Use the dot operator to read and write fields:

package main
 
import "fmt"
 
type Rectangle struct {
    Width  float64
    Height float64
}
 
func main() {
    r := Rectangle{Width: 10, Height: 5}
 
    fmt.Println(r.Width)   // 10
    fmt.Println(r.Height)  // 5
 
    area := r.Width * r.Height
    fmt.Printf("Area: %.1f\n", area) // Area: 50.0
 
    r.Width = 20
    fmt.Println(r.Width) // 20
}

Methods

A method is a function with a receiver — the type it is attached to:

package main
 
import (
    "fmt"
    "math"
)
 
type Circle struct {
    Radius float64
}
 
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}
 
func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}
 
func main() {
    c := Circle{Radius: 5}
    fmt.Printf("Area:      %.2f\n", c.Area())      // 78.54
    fmt.Printf("Perimeter: %.2f\n", c.Perimeter()) // 31.42
}

Idiomatic Go: Attach methods to the type defined in the same package. The receiver name is usually a short abbreviation of the type name (e.g., c for Circle, not self or this).

Pointer Receivers vs Value Receivers

Use a pointer receiver when the method needs to modify the struct, or when the struct is large:

package main
 
import "fmt"
 
type Counter struct {
    count int
}
 
// Pointer receiver — modifies the original
func (c *Counter) Increment() {
    c.count++
}
 
// Value receiver — works on a copy
func (c Counter) Value() int {
    return c.count
}
 
func main() {
    c := Counter{}
    c.Increment()
    c.Increment()
    c.Increment()
    fmt.Println(c.Value()) // 3
}

Idiomatic Go: If any method on a type uses a pointer receiver, all methods should use pointer receivers for consistency.

Struct Embedding

Go uses embedding to achieve composition. An embedded type's methods are promoted to the outer type:

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
    Breed string
}
 
func (d Dog) Speak() string {
    return d.Name + " barks"
}
 
func main() {
    d := Dog{
        Animal: Animal{Name: "Rex"},
        Breed:  "Labrador",
    }
 
    fmt.Println(d.Speak())        // Rex barks (Dog.Speak overrides)
    fmt.Println(d.Animal.Speak()) // Rex makes a sound
    fmt.Println(d.Name)           // Rex (promoted field)
}

Anonymous Structs

Anonymous structs are useful for one-off data shapes, such as test cases:

package main
 
import "fmt"
 
func main() {
    config := struct {
        Host string
        Port int
    }{
        Host: "localhost",
        Port: 8080,
    }
 
    fmt.Printf("%s:%d\n", config.Host, config.Port)
}

Key Takeaways