Always prefer pointer receivers

Go is not an object-oriented language, however, it allows you to implement both Methods and Functions. A Method is simply a Function with a receiver.

GO Methods vs Functions

Look at the code below:

package main

import (
    "fmt"
)

type Contact struct {
    name    string
    surname string
}

func FullName(c Contact) string {
    return c.name + " " + c.surname
}

func main() {
    c := Contact{
        name:    "Massimiliano",
        surname: "Ziccardi",
    }

    fmt.Println("Fullname: ", FullName(c))
}

===== OUTPUT ======
Massimiliano Ziccardi

We have a Contact struct containing the name and the surname of the contact and a function FullName that builds a string composed of name and surname. It would be much nicer to change that function to a method:

package main

import (
    "fmt"
)

type Contact struct {
    name    string
    surname string
}

func (c *Contact) FullName() string {
    return c.name + " " + c.surname
}

func main() {
    c := Contact{
        name:    "Massimiliano",
        surname: "Ziccardi",
    }

    fmt.Println("Fullname: ", c.FullName())
}

===== OUTPUT ======
Massimiliano Ziccardi

Value and Pointers method receivers

In the previous example, I used a pointer receiver (*Contact). However, GO allows us to choose whether to use a pointer or a value receiver.

Let's see what is the main difference and why a pointer receiver should be preferred most of the time.

To understand, let's make another example. This time we are going to use a value receiver:

package main

import "fmt"

type Contact struct {
    name string
    surname string
}

func (n Contact) Name() string {
    return n.name
}

func (n Contact) Surname() string {
    return n.surname
}

func (n Contact) ChangeDetails(name string, surname string) {
    n.surname = surname
    n.name = name
}

func main() {
    c := Contact {
        name: "Jane",
        surname: "Doe",
    }
    fmt.Printf("Details: %s %s\n", c.Name(), c.Surname())
    c.ChangeDetails("John", "Doe")
    fmt.Printf("Details: %s %s\n", c.Name(), c.Surname())
}

===== OUTPUT ======
Details: Jane Doe
Details: Jane Doe

What happened? Why ChangeDetails didn't work?

Explanation

The reason is that, although being very useful, the method receiver syntax is just syntactic sugar: the compiler simply transforms the method into a function that takes the receiver as the first argument.

When you write something like:

func (n Contact) ChangeDetails(name string, surname string) {
    n.surname = surname
    n.name = name
}

the go compiler will transform it to

func ChangeDetails(n Contact, name string, surname string) {
    n.surname = surname
    n.name = name
}

Now, what happened should be clear: when you invoke the ChangeDetail method, it works on a copy of the receiver!

To avoid that and get the expected behaviour, a pointer receiver should be used:

package main

import "fmt"

type Contact struct {
    name    string
    surname string
}

func (n *Contact) Name() string {
    return n.name
}

func (n *Contact) Surname() string {
    return n.surname
}

func (n *Contact) ChangeDetails(name string, surname string) {
    n.surname = surname
    n.name = name
}

func main() {
    c := Contact{
        name:    "Jane",
        surname: "Doe",
    }
    fmt.Printf("Details: %s %s\n", c.Name(), c.Surname())
    c.ChangeDetails("John", "Doe")
    fmt.Printf("Details: %s %s\n", c.Name(), c.Surname())
}

Conclusion

Unless you really know what you are doing, always use a pointer receiver. When you choose either a pointer or a value receiver, use the same receiver type for all the methods.