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.
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
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?
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())
}
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.