Structs and Methods
45 minStructs 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
}