Go Pointer Receiver Nuances

Posted June 12, 2023 by Rohith ‐ 4 min read

A pointer receiver in Go is a method receiver that is defined with a pointer type. It allows a method to modify the value of the receiver itself or access the underlying data the receiver points to. When a method has a pointer receiver, the changes made to the receiver inside the method persist outside the method.

Here are some nuances of using a pointer receiver in Go:

  1. Pointer Semantics: When a method has a pointer receiver, Go automatically converts a value of the receiver type to a pointer to the receiver type, allowing the method to modify the original value. This is known as pointer semantics. If a method modifies the value of the receiver, the changes will be visible outside the method.

Example:

type Person struct {
    Name string
    Age  int
}

// Method with a pointer receiver
func (p *Person) SetName(name string) {
    p.Name = name
}

func main() {
    person := Person{Name: "John", Age: 30}
    fmt.Println("Before:", person.Name) // Output: Before: John

    // Calling the method with a pointer receiver
    person.SetName("Alice")

    fmt.Println("After:", person.Name) // Output: After: Alice
}

In the example above, the SetName method has a pointer receiver p *Person. By using a pointer receiver, the method is able to modify the Name field of the Person struct directly. The change made inside the method is reflected in the person variable outside the method.

  1. Modifying Receiver State: Since a pointer receiver can modify the state of the receiver, it can be useful when you want to change the underlying data of a struct or other mutable types. By using a pointer receiver, you avoid creating a copy of the receiver value and directly work with the original data.

Example:

type Counter struct {
    count int
}

// Method with a pointer receiver
func (c *Counter) Increment() {
    c.count++
}

func main() {
    counter := Counter{count: 0}
    fmt.Println("Before:", counter.count) // Output: Before: 0

    // Calling the method with a pointer receiver
    counter.Increment()

    fmt.Println("After:", counter.count) // Output: After: 1
}

In this example, the Increment method has a pointer receiver c *Counter. The method modifies the count field of the Counter struct directly by incrementing it. As a result, the change made inside the method is visible outside, and the count value is incremented from 0 to 1.

  1. Nil Receivers: A pointer receiver can handle nil receiver values. If a method with a pointer receiver is called on a nil receiver, it will still be executed without causing a runtime error. However, you need to be cautious when using a nil receiver to avoid nil dereference errors.

Example:

type Car struct {
    Brand string
}

// Method with a pointer receiver
func (c *Car) Start() {
    if c != nil {
        fmt.Println("Starting", c.Brand)
    } else {
        fmt.Println("Car is nil")
    }
}

func main() {
    var car *Car = nil

    // Calling the method with a pointer receiver on a nil receiver
    car.Start() // Output: Car is nil
}

In this example, the Start method has a pointer receiver c *Car. Even when car is a nil pointer, the method can still be called without causing a runtime error. Inside the method, it checks if c is nil and handles it accordingly.

  1. Method Overriding: If a method is defined with a pointer receiver, it can only be called on a pointer type. Similarly, if a method is defined with a value receiver, it can only be called on a value type. This can affect method overriding in Go. If an interface specifies a method with a pointer receiver, the implementing type must also have a pointer receiver for that method. The same applies to value receivers.

Example:

type Shape interface {
    Area() float64
}

type Rectangle struct {
    Width  float64
    Height float64
}

// Method with a pointer receiver
func (r *Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    var shape Shape
    rect := Rectangle{Width: 5, Height: 3}

    shape = &rect // Assigning a pointer to a Rectangle

    // Calling the method with a pointer receiver via the interface
    fmt.Println(shape.Area()) // Output: 15
}

In this example, the Rectangle struct implements the Shape interface, which defines the Area method. The Area method is defined with a pointer receiver r *Rectangle. Even when assigning a value of Rectangle to an interface of Shape, the method with a pointer receiver can still be called through the interface.

  1. Convention: It is a convention in Go to use pointer receivers for methods that modify the state of the receiver, and value receivers for methods that do not modify the state. This convention helps in distinguishing between methods that mutate the receiver and those that don’t.

Example:

type Counter struct {
    count int
}

// Method with a pointer receiver
func (c *Counter) Increment() {
    c.count++

It’s important to consider these nuances when deciding whether to use a pointer receiver or a value receiver for your methods in Go. Understanding the behavior of pointer receivers helps you design your code in a way that best suits your requirements and avoids unnecessary copying of data.

quick-references blog

Subscribe For More Content