loading...
Cover image for Go's method receiver: Pointer vs Value

Go's method receiver: Pointer vs Value

chen profile image Chen ・4 min read

Introduction

This topic is huge, and there is plenty of information online. This blog tries to keep it short, and useful for experienced programmers that are new to Go.

Coming to Go from Python introduced me to a new concept I didn't had to put thought into.
Python is a pass-by-object-reference language. You have no direct control over that.
What this means is, when you pass an object (everything in Python is an object) to a function,
you pass a reference to the object itself.

We can see it using the id() function, which returns the identity of an object. This identity has to be unique and constant for this object during its lifetime.

>>> def action_with_string(s):
...     print(id(s))
...
>>> v = "sample string"
>>> id(v)
4360321520
>>> action_with_string(v)
4360321520
>>>

# A list example

>>> ls = list()
>>> def append_list(l):
...     l.append(1)
...
>>> append_list(ls)
>>> ls
[1]
>>> append_list(ls)
>>> ls
[1, 1]

The object inside a function is the same as the caller object.
Whatever you pass, can be mutated (as long as it is mutuable). It is discussed on StackOverflow: Python functions call by reference, if you are interested reading further.

With Go, when you define a method on a struct, you choose if the receiver (the object which the method is executed on, kind of self in Python) is of type Value or a Pointer.

What does this means, anyway?

In simple terms, value receiver makes a copy of the type and pass it to the function. The function stack now holds an equal object but at a different location on memory.

Pointer receiver passes the address of a type to the function. The function stack has a reference to the original object.

A simple example shows the difference.

package main

import (
    "fmt"
)

type Bike struct {
    Model string
    Size int
}

func ValueReceiver(b Bike) {
    fmt.Printf("VALUE :: The address of the received bike is: %p\n", &b)
    // Address of the object is different than the ones in main
    // Changing the object inside the scope here won't reflect to the caller
    b.Model = "BMW"
    fmt.Println("Inside ValueReceiver model: ", b.Model)
}

func PointerReceiver(b *Bike) {
    fmt.Printf("POINTER :: The address of the received bike is: %p\n", b)
    // Address of the object is the same as the ones in main
    b.Model = "BMW"
    fmt.Println("Inside PointerReceiver model: ", b.Model)
}

func main() {
    v := Bike{"Honda CBR", 650}
    p := &Bike{"Suzuki V-Storm", 650}

    fmt.Printf("Value object address in main: %p\nPointer object address in main: %p\n\n", &v, p)
    ValueReceiver(v)
    fmt.Println("Value model outside the function: ", v.Model)

    fmt.Println("")
    PointerReceiver(p)
    fmt.Println("Pointer model outside the function: ", p.Model)
}


// OUTPUT
Value object address in main: 0x40a0e0
Pointer object address in main: 0x40a0f0

VALUE :: The address of the received bike is: 0x40a100
Inside ValueReceiver model:  BMW
Value model outside the function:  Honda CBR

POINTER :: The address of the received bike is: 0x40a0f0
Inside PointerReceiver model:  BMW
Pointer model outside the function:  BMW

So when should you use what?

Regardless of what you choose, it is best practice to keep uniformity of the struct methods. If the struct uses both types, it is hard to track which methods uses which types, especially if you weren't the one who wrote the code.
So try your best they all use the same receiver for consistency.

Pointers

  • If you want to share a value with it's methods

If the method mutates the state of the type, you must use a pointer or it won't work as expected. Value receiver changes are local to the copied object and lives only inside the function scope .

  • If the struct is very large (optimization)

Large structs with multiple fields may be costy to copy everytime they need to be passed around.
If you are in such case, I think you should consider breaking it down to smaller pieces. If that's not possible, use pointer.
Optimization usaully adds complexity. Always think twice before you use it.

Note that pointers are not safe for concurrency; hence you need to handle it yourself using synchronous mechanism such as channels, or the use of atomic or sync builtin packages.

Values

  • If you don't want to share a value, use value receiver.

Value receivers are safe for concurrent access. One of the biggest Go advantages is concurrency, so this is huge plus.
You never can know when you would need it, and if you write library code you can be sure someone at some point will use it concurrently.

Summary

This was an interesting subject for new comers like me. It is a new concept I didn't deal with prior to developing in Go.
After researching the subject thru blogs, documentation, videos and almighty Stackoverflow I have came up with a small set of "rule of thumbs" to remember:

  1. Use the same receiver type for all your methods. This isn't always feasible, but try to.
  2. Methods defines a behavior of a type; if the method uses a state (updates / mutates) use pointer receiver.
  3. If a method don't mutate state, use value receiver.
  4. Functions operates on values; functions should not depend on the state of a type.

Here's a list of great resources if you want to explore deeper:

Posted on by:

chen profile

Chen

@chen

I'm devopser with passion to code. Everyday is an opportunity to learn something new.

Discussion

markdown guide