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 withfmt. - Go favors composition over inheritance — embed types rather than extending them.