Golang is not an object-oriented programming language. But you can define methods on types using receiver functions. Now the question that comes to your mind is, what is a receiver function? is it different from normal function?
In this blog, you will learn how methods are defined in Golang using receiver functions, and if they are pure functions as they claim to be.
How Methods are defined in Golang
Before discussing what methods look like in Golang, first define a struct
named Person
. As we know, every person at least has a name and age. We define the name
and age
fields in the Person
struct in Golang.
type Person struct{
name string
age int
}
Now, let’s define a method birthYear()
on the Person
struct to calculate the birth year of that person.
func (p Person) birthYear() int {
year, _, _ := time.Now().Date()
return year - p.age
}
Here you can see that birthYear
is a regular function with a receiver p
of type Person
. The receiver is defined between the func
keyword and the function name itself. Due to this receiver, now birthYear
function acts as a method of the Person
type.
func main() {
p := Person{"hrishikesh", 24}
fmt.Println(p.birthYear())
}
You can now use the birthYear()
method on the Person
type to calculate the birth year of that person.
Is methods are functions in Golang
As you can see methods are nothing but functions with a receiver. You can also rewrite the above birthYear
method as a function. Let’s see how it turns out.
func birthYearFunc(p Person) int{
year, _, _ := time.Now().Date()
return year - p.age
}
func main() {
p := Person{"hrishi", 24}
fmt.Println(birthYearFunc(p))
}
One very important thing I want to point out is that you can define methods on types that are defined inside the same package. You can’t define methods on types from other packages. For example, you can define methods on the Person
type, as it is defined on the main
package. But you can’t define any method on string
or int
types which is defined in other packages.
Understand Method and pointer indirection in Golang
First, let’s make a changeName
method to change the name of a Person
.
func (p *Person) changeName(newName string) {
p.name = newName
}
func main() {
p := Person{"hrishi", 24}
p.changeName("John")
fmt.Println(p)
}
// Output
{John 24}
Usually, when you pass an argument to a function(or methods), a copy of that value is received by the function. When the function modifies the value, the original value doesn’t change.
on the contrary, if you pass a pointer to the function argument, the actual value is changed.
Here I pass the pointer of the *Person
type as a receiver to the changeName
method. When the changeName
method modifies p
, the actual value of p
got changed. This is because p is not a copy but a pointer reference(i.e. *Person
) to the Person
type. That’s why, you can see the name is changed at the output.
Pointer methods are useful when you want to modify something and hence, they are most common in the Golang world. Let’s convert the changeName
method into a function.
func changeNameFunc(p *Person, newName string) {
p.name = newName
}
func main() {
p := Person{"hrishi", 24}
changeNameFunc(&p, "John")
fmt.Println(p)
}
// Output
{John 24}
Can you spot a difference in the code examples? In the previous example, although we define the changeName
method on a pointer receiver, you can use it with the value p
without explicitly converting it to a pointer first. In this case, the Golang compiler automatically converts the value p
into its pointer &p
.
p.changeName("John")
// What Go does internally
(&p).changeName("John")
But in the case of chnageNameFunc
function, as the argument needs a pointer of type *Person
, we have to explicitly convert p
into &p
pointer and then pass the argument to run the code.
Remember, If a function takes a pointer as an argument, you have to pass a pointer. But methods can use both. This difference in method and function is called Methods and Pointer indirection in Golang.
Similarly, if a function takes a value argument, you have to pass a value. But methods can accept both value and pointer and convert into both when necessary. Let’s modify the birthYear
method and try to invoke it with a pointer.
func main() {
p := Person{"hrishi", 24}
pp := &p
fmt.Println(pp.birthYear())
}
Here pp
is a pointer to the p
value. When we call pp.birthYear()
method, the Golang compiler converts the pointer into a value and calls the method as (*pp).birthYear()
.
But if you try to pass the pp
pointer to the birthYearFunc
function, it gives you an error. BirthYearFunc()
only accepts a value as its argument.
func main() {
p := Person{"hrishi", 24}
pp := &p
fmt.Println(birthYearfunc(*pp))
}
Conclusion
If you understand method and pointer indirection in Golang using this blog, please consider sharing with your peers. Subscribe to the newsletter to get a weekly newsletter with this type of programming content delivered to your inbox.