intermediate Step 6 of 15

Structs and Methods

Go Programming

Structs and Methods

Go does not have classes, but it has structs — composite types that group related data together — and methods that can be defined on any type. This approach to object-oriented programming is simpler than traditional class-based OOP but equally powerful. Structs with methods, combined with interfaces, provide all the abstraction and encapsulation you need. Go encourages composition over inheritance, which leads to flatter, more maintainable code hierarchies.

Defining Structs

package main

import "fmt"

type User struct {
    ID       int
    Name     string
    Email    string
    Age      int
    Active   bool
}

func main() {
    // Create struct instances
    user1 := User{
        ID:    1,
        Name:  "Alice",
        Email: "alice@example.com",
        Age:   30,
        Active: true,
    }

    user2 := User{ID: 2, Name: "Bob", Email: "bob@example.com"}
    // Active defaults to false (zero value)

    // Access fields
    fmt.Println(user1.Name)
    user1.Age = 31  // Modify

    // Pointer to struct
    userPtr := &user1
    userPtr.Name = "Alice Smith"  // Go auto-dereferences

    // Anonymous struct (one-off use)
    config := struct {
        Host string
        Port int
    }{
        Host: "localhost",
        Port: 8080,
    }
    fmt.Println(config.Host)

    fmt.Println(user1, user2)
}

Methods

type Rectangle struct {
    Width, Height float64
}

// Value receiver — works on a copy
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Pointer receiver — can modify the struct
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func (r Rectangle) String() string {
    return fmt.Sprintf("Rectangle(%.1f x %.1f)", r.Width, r.Height)
}

rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())       // 50
fmt.Println(rect.Perimeter())  // 30
rect.Scale(2)
fmt.Println(rect)              // Rectangle(20.0 x 10.0)

// Composition (embedding) — Go's alternative to inheritance
type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

type Dog struct {
    Animal  // Embedded — Dog "has an" Animal
    Breed string
}

func (d Dog) Fetch(item string) string {
    return d.Name + " fetches the " + item
}

dog := Dog{
    Animal: Animal{Name: "Buddy"},
    Breed:  "Lab",
}
fmt.Println(dog.Speak())         // "Buddy makes a sound" (promoted method)
fmt.Println(dog.Fetch("ball"))   // "Buddy fetches the ball"
Pro tip: Use pointer receivers (func (r *MyType) Method()) when the method needs to modify the receiver or when the struct is large (to avoid copying). Use value receivers for small structs and methods that only read data. Be consistent: if any method on a type uses a pointer receiver, all methods should.

Key Takeaways

  • Structs group related data; methods are defined on types using receivers: func (r Rect) Area() float64.
  • Pointer receivers (*T) can modify the struct; value receivers (T) work on a copy.
  • Composition via embedding replaces inheritance: embed a struct to promote its fields and methods.
  • Implement the String() method to customize how your type is printed with fmt.
  • Go favors composition over inheritance — embed types rather than extending them.