In Go, we can attach methods (functions) onto structs to make them behave like objects in other languages.
Objects own data (they have variables) and perform actions (they have functions).
Each method has a receiver type which is the type of the struct:
package main
import "fmt"
type person struct {
name string
age int
xPosition int
yPosition int
}
func (p *person) describeYourself() {
fmt.Printf("My name is %s. I am %d years old.\nYou can find me at coordinates (%d, %d).\n", p.name, p.age, p.xPosition, p.yPosition)
}
func (p *person) moveTo(x, y int) {
p.xPosition = x
p.yPosition = y
fmt.Printf("Moving %s to (%d, %d)...\n", p.name, x, y)
}
func main() {
me := person{name: "James", age: 34}
me.describeYourself()
me.moveTo(3, 4)
me.describeYourself()
}
Pointer receiver type vs. normal receiver type
We can also use the normal receiver type person instead of the pointer receiver type *person:
package main
import "fmt"
type person struct {
name string
age int
xPosition int
yPosition int
}
func (p person) describeYourself() {
fmt.Printf("My name is %s. I am %d years old.\nYou can find me at coordinates (%d, %d).\n", p.name, p.age, p.xPosition, p.yPosition)
}
func (p person) moveTo(x, y int) {
p.xPosition = x
p.yPosition = y
fmt.Printf("Moving %s to (%d, %d)...\n", p.name, x, y)
}
func main() {
me := person{name: "James", age: 34}
me.describeYourself()
me.moveTo(3, 4)
me.describeYourself()
}
The pointer receiver type *person is preferable over receiver type person as this avoids copying on method calls and allows the method to mutate the receiving struct:
package main
import "fmt"
type person struct {
name string
age int
}
func (p person) changeName(newName string) {
p.name = newName
}
func (p *person) changeNameByPointer(newName string) {
p.name = newName
}
func personInfo(p person) {
fmt.Printf("My name is %s. I am %d years old.\n", p.name, p.age)
}
func main() {
person1 := person{"John", 12}
person2 := person{"Mary", 17}
personInfo(person1)
personInfo(person2)
person1.changeName("David")
person2.changeNameByPointer("Lisa")
personInfo(person1)
personInfo(person2)
}
Notice that the changeName method used by the person1 struct named John is unable to change its name field. The changeNameByPointer method is however able to change the name field of the underlying struct person2 with the initial name Mary.
Methods when combined with interfaces in Go produce the repository pattern which allows us to swap out completely unrelated structs as long as they implement the same methods e.g. if both person and animal structs have a moveTo method, they can be used interchangeably for different results in our program.