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.,
cforCircle, notselforthis).
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
- Structs group related fields into a named type; methods attach behaviour.
- Use pointer receivers when methods need to mutate state or the struct is large.
- Embedding provides composition: promoted fields and methods keep the API flat.
- Prefer named field initialisers (
Field: value) over positional ones.