Go Methods and Receivers
Methods in Go are functions that are attached to specific types, allowing you to add behavior to structs and other custom types. Go's method system is unique in its approach to object-oriented programming, using receivers to bind functions to types. Understanding methods and receivers is fundamental to writing idiomatic Go code and implementing object-oriented patterns. This comprehensive guide will teach you everything you need to know about Go's method system.
Understanding Methods in Go
What Are Methods?
Methods in Go are functions that have a special receiver parameter that binds the function to a specific type. They provide:
- Type-specific behavior - Functions that operate on specific types
- Encapsulation - Data and behavior grouped together
- Code organization - Related functionality organized by type
- Polymorphism - Different types can implement the same method interface
- Clean APIs - Intuitive interfaces for type operations
Go's Method System Characteristics
Go's method system has several distinctive features:
Receiver Syntax
Methods use a special receiver parameter syntax that binds them to types.
Value vs Pointer Receivers
Methods can have value receivers (copy) or pointer receivers (reference).
Method Sets
Go defines method sets that determine which methods are available on types.
Implicit Interface Satisfaction
Methods enable types to satisfy interfaces implicitly.
Method Definition and Syntax
Basic Method Definition
The func
Keyword with Receivers
Methods are defined using the func
keyword with a receiver parameter before the function name.
Receiver Parameter Syntax
The receiver parameter specifies which type the method belongs to.
package main
import "fmt"
func main() {
// Basic method definition and syntax examples
fmt.Println("Basic method definition and syntax examples:")
// Define a struct
type Rectangle struct {
Width float64
Height float64
}
// Method with value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Method with value receiver
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Method with value receiver
func (r Rectangle) String() string {
return fmt.Sprintf("Rectangle{Width: %.2f, Height: %.2f}", r.Width, r.Height)
}
// Create rectangle instance
rect := Rectangle{Width: 5.0, Height: 3.0}
// Call methods
fmt.Printf("Rectangle: %s\n", rect.String())
fmt.Printf("Area: %.2f\n", rect.Area())
fmt.Printf("Perimeter: %.2f\n", rect.Perimeter())
// Output:
// Rectangle: Rectangle{Width: 5.00, Height: 3.00}
// Area: 15.00
// Perimeter: 16.00
// Define another struct
type Circle struct {
Radius float64
}
// Method with value receiver
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
// Method with value receiver
func (c Circle) Circumference() float64 {
return 2 * 3.14159 * c.Radius
}
// Method with value receiver
func (c Circle) String() string {
return fmt.Sprintf("Circle{Radius: %.2f}", c.Radius)
}
// Create circle instance
circle := Circle{Radius: 2.5}
// Call methods
fmt.Printf("Circle: %s\n", circle.String())
fmt.Printf("Area: %.2f\n", circle.Area())
fmt.Printf("Circumference: %.2f\n", circle.Circumference())
// Output:
// Circle: Circle{Radius: 2.50}
// Area: 19.63
// Circumference: 15.71
}
Method with Different Receiver Types
Value Receivers
Value receivers receive a copy of the struct value.
Pointer Receivers
Pointer receivers receive a reference to the struct value.
package main
import "fmt"
func main() {
// Method with different receiver types examples
fmt.Println("Method with different receiver types examples:")
// Define a struct
type Counter struct {
value int
}
// Method with value receiver (read-only)
func (c Counter) GetValue() int {
return c.value
}
// Method with value receiver (read-only)
func (c Counter) String() string {
return fmt.Sprintf("Counter{value: %d}", c.value)
}
// Method with pointer receiver (can modify)
func (c *Counter) Increment() {
c.value++
}
// Method with pointer receiver (can modify)
func (c *Counter) Decrement() {
c.value--
}
// Method with pointer receiver (can modify)
func (c *Counter) SetValue(value int) {
c.value = value
}
// Method with pointer receiver (can modify)
func (c *Counter) Reset() {
c.value = 0
}
// Create counter instance
counter := Counter{value: 0}
// Use value receiver methods
fmt.Printf("Initial counter: %s\n", counter.String())
fmt.Printf("Initial value: %d\n", counter.GetValue())
// Output:
// Initial counter: Counter{value: 0}
// Initial value: 0
// Use pointer receiver methods
counter.Increment()
counter.Increment()
counter.Increment()
fmt.Printf("After incrementing: %s\n", counter.String())
// Output: After incrementing: Counter{value: 3}
counter.Decrement()
fmt.Printf("After decrementing: %s\n", counter.String())
// Output: After decrementing: Counter{value: 2}
counter.SetValue(10)
fmt.Printf("After setting value: %s\n", counter.String())
// Output: After setting value: Counter{value: 10}
counter.Reset()
fmt.Printf("After reset: %s\n", counter.String())
// Output: After reset: Counter{value: 0}
// Demonstrate value vs pointer receiver behavior
fmt.Println("\nValue vs pointer receiver behavior:")
// Create a new counter
counter2 := Counter{value: 5}
// Value receiver method doesn't modify original
func (c Counter) TryModify() {
c.value = 100 // This doesn't affect the original
}
counter2.TryModify()
fmt.Printf("After TryModify (value receiver): %s\n", counter2.String())
// Output: After TryModify (value receiver): Counter{value: 5}
// Pointer receiver method modifies original
func (c *Counter) ActuallyModify() {
c.value = 100 // This affects the original
}
counter2.ActuallyModify()
fmt.Printf("After ActuallyModify (pointer receiver): %s\n", counter2.String())
// Output: After ActuallyModify (pointer receiver): Counter{value: 100}
}
Value vs Pointer Receivers
Understanding Receiver Types
Value Receivers
Value receivers receive a copy of the struct value, making them safe for concurrent access but unable to modify the original.