Methods in Go

By

Learn how methods work in Go, functions attached to a struct, and the difference between a value receiver that copies and a pointer receiver.

~~~

A function can be assigned to a struct and in this case we call it method. Go has no classes, so methods are how you attach behavior to your types.

The syntax is a regular function declaration, plus a receiver between the func keyword and the function name. The receiver is the struct instance the method is called on.

Example:

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) Speak() {
	fmt.Println("Hello from " + p.Name)
}

func main() {
	flavio := Person{Age: 39, Name: "Flavio"}
	flavio.Speak()
}

Running this prints:

Hello from Flavio

Inside Speak(), p works like any other parameter. It gives you access to the fields of the instance the method was called on.

Value receivers and pointer receivers

Methods can be declared to be pointer receiver or value receiver.

The above example shows a value receiver, it receives a copy of the struct instance.

This would be a pointer receiver that receives the pointer to the struct instance:

func (p *Person) Speak() {
	fmt.Println("Hello from " + p.Name)
}

For a method that only reads data, like Speak(), both work the same way. The difference shows up when the method writes.

The pitfall: mutations on a value receiver are lost

Say we add a method that increments the age:

func (p Person) Birthday() {
	p.Age++
}

It compiles, it runs, and it does nothing useful:

flavio := Person{Age: 39, Name: "Flavio"}
flavio.Birthday()
fmt.Println(flavio.Age) //39

The method incremented p.Age, but p was a copy. The copy is thrown away when the method returns, and flavio never changes.

The fix is a pointer receiver:

func (p *Person) Birthday() {
	p.Age++
}

Now the method works on the original struct:

flavio := Person{Age: 39, Name: "Flavio"}
flavio.Birthday()
fmt.Println(flavio.Age) //40

Notice we still call it as flavio.Birthday(), not (&flavio).Birthday(). Go takes the address for us when the variable is addressable.

Which one should you pick?

Use a pointer receiver when the method needs to modify the struct, or when the struct is large and copying it on every call would be wasteful.

My advice is to also keep receivers consistent. If one method on a type needs a pointer receiver, give pointer receivers to all the methods on that type. Mixing the two makes it harder to reason about which calls can mutate your data.

Tagged: Go ยท All topics
~~~

Related posts about go: