Loading...
Loading...

Go Methods Tutorial

Methods in Go are functions that operate on specific types (receivers). They provide a way to associate behavior with data structures, enabling object-oriented programming patterns while maintaining Go's simplicity and type safety.

1. Method Fundamentals

1.1 Method Declaration

Methods are declared with a special receiver parameter that appears between the func keyword and the method name.

// Basic method syntax
func (receiver ReceiverType) MethodName(parameters) returnTypes {
    // Method body
}

// Example with Rectangle type
type Rectangle struct {
    Width, Height float64
}

// Area method with value receiver
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Perimeter method with value receiver
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

1.2 Method Invocation

Methods are called using dot notation on an instance of the receiver type.

rect := Rectangle{Width: 10, Height: 5}

// Calling methods
area := rect.Area()
perimeter := rect.Perimeter()

fmt.Printf("Area: %.2f, Perimeter: %.2f\n", area, perimeter)
// Output: Area: 50.00, Perimeter: 30.00

1.3 Value vs Pointer Receivers

Methods can be declared with either value or pointer receivers, which affects whether they can modify the receiver.

// Value receiver (works on a copy)
func (r Rectangle) DoubleWidth() {
    r.Width *= 2 // Won't affect original
}

// Pointer receiver (works on original)
func (r *Rectangle) DoubleHeight() {
    r.Height *= 2 // Modifies original
}

// Usage
rect := Rectangle{10, 5}
rect.DoubleWidth()
fmt.Println(rect.Width) // Still 10

rect.DoubleHeight()
fmt.Println(rect.Height) // Now 10

2. Choosing Receiver Types

2.1 When to Use Value Receivers

Value receivers are appropriate when:

  • The method doesn't need to modify the receiver
  • The receiver type is small (copying is cheap)
  • You want to guarantee immutability
type Point struct {
    X, Y float64
}

// Value receiver - calculates distance without modifying Point
func (p Point) DistanceToOrigin() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

2.2 When to Use Pointer Receivers

Pointer receivers are necessary when:

  • The method needs to modify the receiver
  • The receiver type is large (avoid copying overhead)
  • The type contains synchronization primitives (e.g., mutexes)
type BankAccount struct {
    balance float64
    mutex   sync.Mutex
}

// Pointer receiver - modifies account and uses mutex
func (a *BankAccount) Deposit(amount float64) {
    a.mutex.Lock()
    defer a.mutex.Unlock()
    a.balance += amount
}

2.3 Consistency in Receiver Types

For a given type, it's good practice to be consistent with receiver types:

type Counter struct {
    value int
}

// All methods use pointer receivers for consistency
func (c *Counter) Increment() {
    c.value++
}

func (c *Counter) Decrement() {
    c.value--
}

func (c *Counter) Value() int {
    return c.value
}

3. Methods on Non-Struct Types

3.1 Methods on Basic Types

You can define methods on any type declared in your package, including basic types.

// Declare a new type based on int
type Celsius float64

// Method to convert Celsius to Fahrenheit
func (c Celsius) Fahrenheit() Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

// Usage
temp := Celsius(100)
fmt.Println(temp.Fahrenheit()) // 212

3.2 Methods on Slice Types

Custom slice types can have methods for specialized operations.

type StringSlice []string

// Method to check if slice contains a string
func (ss StringSlice) Contains(s string) bool {
    for _, v := range ss {
        if v == s {
            return true
        }
    }
    return false
}

// Usage
names := StringSlice{"Alice", "Bob", "Charlie"}
fmt.Println(names.Contains("Bob")) // true

3.3 Methods on Function Types

You can even attach methods to function types for advanced patterns.

type Predicate func(int) bool

// Method to negate a predicate
func (p Predicate) Negate() Predicate {
    return func(n int) bool {
        return !p(n)
    }
}

// Usage
isEven := Predicate(func(n int) bool { return n%2 == 0 })
isOdd := isEven.Negate()

fmt.Println(isEven(4), isOdd(4)) // true false

4. Method Sets and Interfaces

4.1 Understanding Method Sets

A type's method set determines which methods can be called on values of that type.

type Shape interface {
    Area() float64
    Perimeter() float64
}

// Rectangle satisfies Shape implicitly
func (r Rectangle) Area() float64 { /* ... */ }
func (r Rectangle) Perimeter() float64 { /* ... */ }

// Circle also satisfies Shape
type Circle struct { Radius float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

4.2 Pointer vs Value Method Sets

The method set for *T includes all methods declared with both value and pointer receivers.

type Counter struct { value int }

func (c Counter) Value() int { return c.value }
func (c *Counter) Increment() { c.value++ }

// Value method set: Value()
// Pointer method set: Value(), Increment()

var c Counter
c.Increment() // Works - Go automatically takes address
(&c).Value() // Also works

4.3 Interface Satisfaction Rules

Key rules for interface implementation:

  • T implements all methods declared with T receiver
  • *T implements all methods declared with either T or *T receiver
  • Interface values can hold either T or *T if T implements the interface

5. Advanced Method Patterns

5.1 Method Chaining

Methods can return the receiver to enable fluent interfaces.

type StringBuilder struct {
    buffer strings.Builder
}

func (sb *StringBuilder) WriteString(s string) *StringBuilder {
    sb.buffer.WriteString(s)
    return sb
}

func (sb *StringBuilder) String() string {
    return sb.buffer.String()
}

// Usage
result := new(StringBuilder).
    WriteString("Hello").
    WriteString(" ").
    WriteString("World").
    String()

5.2 Factory Methods

Methods can act as factories for related types.

type Matrix struct {
    data [][]float64
}

// Factory method on Matrix to create identity matrix
func (m Matrix) Identity(size int) Matrix {
    identity := make([][]float64, size)
    for i := range identity {
        identity[i] = make([]float64, size)
        identity[i][i] = 1.0
    }
    return Matrix{data: identity}
}

5.3 Method Expressions

Methods can be referenced as first-class functions.

// Method expression syntax: Type.MethodName
areaFunc := Rectangle.Area // Type: func(Rectangle) float64

rect := Rectangle{10, 5}
fmt.Println(areaFunc(rect)) // 50

// Can also work with pointer receivers
scaleFunc := (*Rectangle).DoubleHeight
scaleFunc(&rect)
fmt.Println(rect.Height) // 10

6. Performance Considerations

6.1 Receiver Size Impact

The size of the receiver affects method call performance.

// Small struct - value receiver is fine
type Point struct { X, Y float64 } // 16 bytes

// Large struct - pointer receiver preferred
type Image struct {
    pixels [1000][1000]byte // 1,000,000 bytes
}

func (p Point) Distance() float64 { /* ... */ } // OK
func (img *Image) Process() { /* ... */ }      // Better

6.2 Inlining Opportunities

Small methods may be inlined by the compiler for better performance.

// Likely to be inlined
func (p Point) Add(q Point) Point {
    return Point{p.X + q.X, p.Y + q.Y}
}

// Less likely to be inlined
func (img *Image) ComplexOperation() {
    // Many lines of complex code
}

6.3 Interface Method Calls

Interface method calls have a small runtime overhead compared to direct calls.

// Direct call (fastest)
rect := Rectangle{10, 5}
area := rect.Area()

// Interface call (slightly slower)
var shape Shape = rect
area = shape.Area()

7. Common Method Idioms

7.1 String Representation

The String() method customizes how a type is printed.

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}

// Usage
person := Person{"Alice", 30}
fmt.Println(person) // Alice (30 years)

7.2 Error Reporting

The Error() method enables custom error types.

type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

// Usage
err := ValidationError{"Email", "invalid format"}
fmt.Println(err) // Email: invalid format

7.3 Sorting Interface

Methods can implement standard interfaces like sort.Interface.

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

// Usage
people := []Person{{"Bob", 31}, {"Alice", 25}}
sort.Sort(ByAge(people))
0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home