Back to Curriculum

Structs and Methods

📚 Lesson 6 of 16 ⏱️ 45 min

Structs and Methods

45 min

Structs group related data fields under a single type, enabling you to create custom types that represent real-world entities. Structs are value types—assigning a struct copies all fields. Structs can have fields of any type, including other structs. Structs are Go's way of creating custom types and are fundamental to Go programming. Understanding structs enables you to model data effectively.

Methods are functions with a receiver parameter that associate functions with types. Methods enable you to define behavior for types. Go supports both value receivers (operate on copies) and pointer receivers (operate on the original). Pointer receivers are more common as they're more efficient and can modify the receiver. Understanding methods enables object-oriented-style programming in Go.

Value receivers operate on copies of the struct, while pointer receivers operate on the original struct. Use value receivers for small structs or when you don't need to modify the receiver. Use pointer receivers when you need to modify the receiver or when structs are large (to avoid copying). The choice affects method sets and interface satisfaction. Understanding receiver types helps you write efficient, correct code.

Embedding allows structs to include other structs, enabling composition over inheritance. Embedded fields are promoted to the outer struct, allowing direct access. Embedding is Go's way of achieving code reuse without inheritance. Embedded types' methods are also promoted. Understanding embedding helps you design flexible, composable types.

Best practices include using pointer receivers for methods that modify the receiver, using value receivers for small, immutable types, using embedding for composition, keeping structs focused, and using meaningful field names. Structs should represent cohesive data. Understanding structs and methods enables you to write effective, idiomatic Go code.

Key Concepts

  • Structs group related data fields under a single type.
  • Methods are functions with receiver parameters.
  • Value receivers operate on copies; pointer receivers operate on originals.
  • Embedding enables composition over inheritance.
  • Methods enable behavior to be associated with types.

Learning Objectives

Master

  • Creating structs to group related data
  • Defining methods with value and pointer receivers
  • Understanding when to use value vs pointer receivers
  • Using embedding for composition

Develop

  • Object-oriented design thinking in Go
  • Understanding composition vs inheritance
  • Designing maintainable, composable types

Tips

  • Use pointer receivers for methods that modify the receiver.
  • Use value receivers for small, immutable types.
  • Use embedding for composition—Go's alternative to inheritance.
  • Keep structs focused and cohesive.

Common Pitfalls

  • Using value receivers for large structs, causing performance issues.
  • Not understanding receiver types, causing unexpected behavior.
  • Trying to use inheritance—Go uses composition instead.
  • Creating structs that are too large or do too much.

Summary

  • Structs group related data fields into custom types.
  • Methods associate behavior with types via receivers.
  • Pointer receivers are more common and efficient.
  • Embedding enables composition over inheritance.
  • Understanding structs and methods enables effective Go programming.

Exercise

Create structs and methods to demonstrate object-oriented concepts in Go.

package main

import (
    "fmt"
    "math"
)

// Person struct
type Person struct {
    Name string
    Age  int
    City string
}

// Method with value receiver
func (p Person) Greet() string {
    return fmt.Sprintf("Hello, I'm %s from %s", p.Name, p.City)
}

// Method with pointer receiver (can modify the struct)
func (p *Person) Birthday() {
    p.Age++
}

// Circle struct
type Circle struct {
    Radius float64
}

// Method for Circle
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Circumference() float64 {
    return 2 * math.Pi * c.Radius
}

// Embedded struct (composition)
type Employee struct {
    Person
    Salary float64
    Title  string
}

// Method for Employee
func (e Employee) GetInfo() string {
    return fmt.Sprintf("%s, %s, Salary: $%.2f", e.Name, e.Title, e.Salary)
}

// Interface
type Shape interface {
    Area() float64
}

// Rectangle struct
type Rectangle struct {
    Width  float64
    Height float64
}

// Method for Rectangle
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Function that works with any Shape
func printArea(s Shape) {
    fmt.Printf("Area: %.2f
", s.Area())
}

func main() {
    // Creating struct instances
    person := Person{
        Name: "Alice",
        Age:  25,
        City: "Boston",
    }
    
    fmt.Println(person.Greet())
    
    // Using pointer receiver
    person.Birthday()
    fmt.Printf("After birthday: %s is now %d years old
", person.Name, person.Age)
    
    // Circle example
    circle := Circle{Radius: 5.0}
    fmt.Printf("Circle area: %.2f
", circle.Area())
    fmt.Printf("Circle circumference: %.2f
", circle.Circumference())
    
    // Employee with embedded Person
    employee := Employee{
        Person: Person{
            Name: "Bob",
            Age:  30,
            City: "New York",
        },
        Salary: 75000.0,
        Title:  "Software Engineer",
    }
    
    fmt.Println(employee.GetInfo())
    fmt.Println(employee.Greet()) // Can access Person's methods
    
    // Interface usage
    rectangle := Rectangle{Width: 4.0, Height: 6.0}
    printArea(circle)    // Circle implements Shape
    printArea(rectangle) // Rectangle implements Shape
}

Code Editor

Output