Anatomy of methods in Go

Dinesh Silwal
8 min readJan 18, 2021

I was very confused at the beginning when I read an article that says “Go is object-oriented”. Sometimes another article claims that no object-oriented programming can be done with Go, only because it does not have any classes. So I am writing this article just to clarify this topic. Is Go object-oriented, or not?
For example, if you come from C, clearly Go has lots more object-oriented programming features. Coming from Java, Go code doesn’t look much object-oriented. What you need to do in this case is stop thinking in terms of the “other language”, and think with a Go mindset first.

Here’s my answer: Yes, Go is object-oriented, and but in a refreshing a sane way.

Go does not support the Object-Oriented paradigm but In go, structure :that we build resembles the class architecture. To add methods to a structure, we need to use functions with a receiver.

Since Go is not based on OOP paradigm, obivously Go does not provide classes, but we can use structures to create objects. But in OOP, classes have properties (fields) as well as behaviors (methods) and we have learned about properties of a struct that are structure fields as shown below:

type person struct {
name string
age int
}

First Lets be clear about the method:

What is the method?

A method is nothing but a function, but it belongs to a certain type. A method is defined with slightly different syntax than a normal function. It required an additional parameter known as a receiver which is a type to which the function belongs. This way, a method (function) can access the properties of the receiver it belongs to (like fields of a struct).

let be more clear down here:

In the above program, we have created a simple struct type Student that has two string fields FirstName and LastName. Then we’ve defined the function fullName which takes two strings arguments and returns a string. The function returns the full name of a student by concatenating these two strings. Then we created a struct “s” of type Student by providing FirstName and LastName fields values. To get the full name of the Student s, we use the fullName function and provided the appropriate arguments. This works, but the sad thing is, every time we need to get the full name of a Student (and there could be hundreds), we need to pass firstName and lastName values to fullName function manually.

A method can solve this problem easily. To convert a function to the method, we just need an extra receiver parameter in the function definition. The syntax for defining a method is as follows.

func (r Type) functionName(…Type) Type {

}

package main

import "fmt"

type Student struct {
FirstName, LastName string
}

func (s Student) fullName() string{
return s.FirstName + s.LastName
}

func main(){
s := Student{FirstName: "davic", LastName: "sharma"}
fmt.Print(s.FirstName, s.LastName)
}

In the above program, we have defined fullName a method that does not take any arguments but returns a string. Using a receiver declaration, this method belongs to the “Student” struct type.

Hence that object will automatically get this method as a property

The receiver of the method is accessible inside the method body. Hence we can access “s” inside the method body of fullName. In the above example, since the receiver is a struct of type Student, we can access any fields of the struct. Like we did in the previous example, we are concatenating FirstName and LastName fields and returning the result.

As a method belongs to a receiver type and it becomes available on that type as a property, we can call that method using Type.methodName(...)syntax. In the above program, we have used s.fullname() to get the full name of a student since the method belongs to the student.

💡 In case of methods, we don’t have to provide properties of struct because method already knows about them.

Let’s define a method with the same name but different behavior || functionality:

package main


import (
"fmt"
"math"
)

type square struct {
length float64
}

type circle struct {
radius float64
}



func (c circle) area() float64{
return math.Pi * c.radius *c.radius
}

func (s square) area() float64{
return s.length * s.length
}

func calArea(s shape) {
switch s.(type) {
case square:
fmt.Println("This is the area of the square: ", s.area())
case circle:
fmt.Println("This is the area of the circle: ", s.area())
}
}

func main() {
c := circle{
radius: 2.5,
}
s := square{
length: 1.5,
}

calArea(c)
calArea(s)

}

What is interface in golang then?

Go language interfaces are different from other languages. In Go language, the interface is a custom type that is used to specify a set of one or more method signatures and the interface is abstract, so you are not allowed to create an instance of the interface. . Or in other words, the interface is a collection of methods as well as it is a custom type.

type interface_name interface{

// Method signatures

}

How to implement interfaces?

In the Go language, it is necessary to implement all the methods declared in the interface for implementing an interface. The go language interfaces are implemented implicitly. And it does not contain any specific keyword to implement an interface just like other languages. As shown in the below example:

package main

import (
"fmt"
"math"
)

type square struct {
length float64
}

type circle struct {
radius float64
}

type shape interface {
area() float64
}

func (c circle) area() float64{
return math.Pi * c.radius *c.radius
}

func (s square) area() float64{
return s.length * s.length
}

func calArea(s shape) {
switch s.(type) {
case square:
fmt.Println("This is the area of the square: ", s.area())
case circle:
fmt.Println("This is the area of the circle: ", s.area())
}
}

func main() {
c := circle{
radius: 2.5,
}
s := square{
length: 1.5,
}

calArea(c)
calArea(s)

}

//From the example above, we can see polymorphism happening.
//That is, the area() function is used for calculating the area of the circle and the square.
//We means the interface is
//implemented in the square and circle types because they both define the interface method: area()

What is a pointer?

Simply put, a pointer is a value that points to the address of another. This is the textbook explanation, but if you’re coming from a language that doesn’t let you talk about the address of a variable, it could very well be written in Cuneiform.

Let’s break this down.

What is a pointer?

A pointer is a value that points to the memory address of another variable. The pointer points to a memory address of a variable, just as a variable represents the memory address of the value.

func main() {
a := 200
b := &a
*b++
fmt.Println(a)
}

On the first line of main we declare a new variable a and assign it the value 200.Next we declare a variable b and assign it the address a. Remember that we don’t know the exact memory location where a is stored, but we can still store a‘s address in b.

The third line is probably the most confusing, because of the strongly typed nature of Go. b contains the address of a variable, but we want to increment the value stored in a. To do this we must dereference, follow the pointer from b to a.

Then we add one the value and store it back in the memory location stored in b.The final line prints the value of a, showing that it has increased to 201.

Pointer concept in Golang?

A pointer is a variable that stores the address of a value, rather than the value itself. If you think of a computer’s memory (RAM) as a JSON object, a pointer would be like the key, and a normal variable would be the value.

{
"pointer": "variableValue"
}
package mainimport "fmt"func main() {
// create a normal string variable
name := "original"
// pass in a pointer to the string variable using '&'
setName(&name, "abcd")
fmt.Println(name)
}
func setName(ptr *string, newName string) {
// dereference the pointer so we can modify the value
// and set the value to "abcd"
*ptr = newName
}

This prints:

abcd

As you can see, because we have a pointer to the address of the variable, we can modify its value, even within the scope of another function. If the value were not a pointer, this would not work:

package mainimport "fmt"func main() {
name := "original"
setNameBroken(name, "abcd")
fmt.Println(name)
}
func setNameBroken(ptr string, newName string) {
ptr = newName
}

prints:

original

Pointers can be useful, but in the same way that they are useful, they can be dangerous. For example, if we dereference a pointer that has no value, the program will panic. For this reason, we always check if an error value is a nil before trying to print it.

Syntax

1. Creating a pointer: &

newString := ""
newStringPointer := &newString

If you print that pointer you will see a memory address.

package mainimport "fmt"func main() {
newString := ""
newStringPointer := &newString
fmt.Println(newStringPointer)
}

prints: 0xc00000e1e0

Which is the memory address of that variable in your machine.

2. Describing a pointer: *

In a function signature or type definition, the * is used to designate that a value is a pointer.

func passPointer(pointer *string) {
}

3. Dereferencing a pointer: *

It can be slightly confusing, but the * is used to describe a pointer and it is also used as an operator to dereference a pointer.

func derefPointer(pointer *string) {
newStringVariable := *pointer
// newStringVariable is just a normal string
}

When Should I Use a Pointer?

Maximum no of ways a pointer is used for the following reasons:

1. A function that mutates one of its parameters

When I call a function that takes a pointer as an argument, I expect that my variable will be mutated. If you aren’t mutating the variable in your function, then you probably shouldn’t be using a pointer.

2. Better Performance

If you have a string that contains an entire novel in memory it gets really expensive to copy that variable each time it is passed to a new function. It may be worthwhile to pass a pointer instead, which will save CPU and memory. This comes at the cost of readability, however, so only make this optimization if you must.

3. Need a Nil Value Option

Sometimes a function needs to know what something’s value is, as well as if it exists or not. I usually use this when reading JSON to know if a field exists or not. For example, if a JSON object is:

{ "name": "abcd" } ----> *name: "abcd"{ "name": "" } ----------> *name: ""{} ----------------------> *name: nil

This article is will much clarify method anatomy, interface(that exhibits polymorphism), and pointer proper use.

--

--