Anatomy of methods in Go

Since there is no class-object architecture and the closest thing to class we have is structure. Function with struct receiver is a way to achieve methods in Go.

Uday Hiwarale
Oct 20, 2018 · 10 min read

Since we don’t have classes in Go, you can say struct will do a job to make objects. But in OOP, classes have properties (fields) and behaviors (methods) and so far we have seen only properties of a struct.

To all those non-OOP programmers, behavior is a action that an object can perform. For example, Dog is a type of Animal and Dog can bark. Hence barking is a behavior of animal class Dog. Hence any objects (instances) of class Dog will have this behavior.

We have seen in structures lesson, especially in the function field section that, struct field can also be a function. We can add a bark field of type function which takes no arguments and returns a string woof woof!. But this does not adhere to the OOP concept as struct fields do not have any idea of struct they belong to. Hence methods come to the rescue.

☛ What is a method

As you know that struct field can also be a function, the concept of method will be very easy for you to understand. A method is nothing but a function, but it belongs to a certain type. A method is defined with different syntax than normal function. It required an additional parameter known as a receiver which is a type to which the method belongs. A method can access properties of the receiver it belongs to.

Let’s see a simple program to get the full name of an Employee using a simple function.

In the above program, we have created a simple struct type Employee which has two string fields FirstName and LastName. Then we defined the function fullName which takes two strings and returns a string. fullname function returns the full name of an employee by concatenating these two strings. Then we created a struct e and used fullName function to get the full name of the employee e.

But the sad thing is, every time we need to get the full name of an employee, we need to pass FirstName and LastName to fullName function manually.

The method solves this problem easily. As we talked, we just need an extra receiver parameter in the function definition.

The syntax for defining a method is

func (r Type) functionName(...Type) Type {
...
}

From the above syntax, we can tell that method and function have the same syntax except for one input argument declaration (r Type) before the function name. Type is any legal type in Go and function arguments and return value are optional.

Let’s create fullName method using the above syntax.

In the above program, we have defined fullName method which does not take any arguments but returns a string. It belongs to a struct type Employee hence we used the argument e which will act as a receiver for Employee struct.

Receiver in the method is accessible inside the method body. Hence we can access e inside the method body. Using that, we can access any fields of struct. In the above program, we are concatenating FirstName and LastName and returning as the full name of the employee.

As a method belongs to a receiver type, we can call that method using Type.methodName(...)syntax. In the above program, we have used e.fullName() to get the full name of an employee as fullName method belongs to e.

This is no different than what we learned in structs lesson where fullName function was a field of struct. But in case of methods, we don’t have to provide properties of struct because method already knows about them.

☛ Same method name

One major difference between function and method is many methods can have the same name while no two functions with the same name can be defined in a package.

Let’s create two struct types Circle and Rectangle and create two methods of the same name Area which calculates the area of their receiver.

In the above program, we have created struct types Rect and Circle and created two methods of the same name Area with receiver type Rect and Circle. We are allowed to use same method names as long as method receivers are of different types. When we call Area method on Rect and Circle, their respective methods get executed.

☛ Pointer receivers

Until now we have seen methods which receive the value of the receiver that means methods only receives a copy of the Type they belong to, for example, a struct in previous examples.

To verify that, we can create a method that changes the value of a struct field. Let’s create a method name changeName that changes the name of Employee struct.

In the above program, we have called method changeName on struct e of type Employee while in the method, we are assigning a new value to the name field.

From the above result, it seems like the method changeName did not do anything with name property of e. This proves that receiver e in method definition was just a copy of the actual struct e(from main method), hence any changes made to did not affect the original struct.

But a method can also accept pointer value of the receiver. Syntax to define a method with pointer receiver is very similar to the normal method. In the below definition, we instructed Go that this method will receive a pointer receiver.

func (r *Type) methodName(...Type) Type {
...
}

Let’s re-write the previous example with a method that receives pointer receiver.

Let’s see what changes we made.

  • We changed the definition of the method to receive a pointer receiver using *. Now method changeName will receive pointer of the receiver, that means original value. Inside method body, we are converting pointer of the receiver to the value of the receiver using *, hence (*e) will be the actual value stored in the memory. Hence any change made on it will be reflected in the original value of the receiver struct.
  • Then we create a pointer ep which points to struct e.
  • While calling changeName method on struct e, we need to call it on the pointer of it which will be ep. Since method belongs to pointer of e rather than the value of e. This will pass the pointer ep to the method instead of value e.

In above program, we created pointer ep to call method on it, but you can use (&e).changeName("Monica Geller")syntax instead of creating new pointer.

This all sound little complex. But don’t worry, Go makes it simple by providing some shortcuts. Let’s rewrite above programming using Go’s shortcuts.

Above program will work just fine like before. So what changed.

  • If a method receives a pointer receiver, you don’t need to using (*e) syntax to deference pointer or get the value of the pointer. You can use simple e which will be the address of the value that pointer points to but Go will understand that you are trying to perform an operation on the value itself and under the hood, it will make e to (*e).
  • Also, you don’t need to call a method on the pointer if the method receives a pointer receiver. Instead, you can use value as it is, e and call a method on it. Go will pass the pointer of e under the hood if the method expects pointer receiver.

You can decide between method with pointer receiver or value receiver depending on your use case. But generally, even if you do not wish to change data of the receiver, methods with pointer receiver are used as no new memory is created for operations. Which happens when passing copy of the receiver, which happens in case of methods with value receiver.

☛ Methods on nested struct

As a struct field also can be a struct, we can define a method on main struct and access nested struct to do anything we want.

If the inner struct implements a method, then you can call a method on it using . (dot) accessor.

But what will happen if the nested field is anonymous? You remember anonymous fields where a field has no name or name of the field is derived from its type. In case if the field is of a struct type, nested struct fields will be promoted.

Let’s see what will happen to the methods in a case when struct field is anonymous.

As we saw from structs lesson that if the nested field is anonymous struct then its fields will be accessible from parent struct. Hence any method that accepts struct receiver will also have access to the promoted fields.

☛ Promoted methods

Like promoted fields on a struct, methods implemented by inner struct is available on parent struct. As we saw in the previous example, Contact field is anonymous. Hence we could access e.phone, the phone number from inner struct as phone field is promoted to Employee struct. In the same scenario, any method implemented by Contact struct will be available on Employee struct. Let’s rewrite the previous example.

We made only one change to changePhone function. Instead of receiving Employee type, method now expects Contact pointer receiver. Since, Contact field is promoted, any method on it will be promoted too. Hence we could use e.changePhone as if the type Employee of struct e implemented changePhone method.

☛ A method can accept both pointer and value

When a function has a value argument, it will only accept the value of the parameter. If you passed a pointer to the function which expects a value, it will not work. This is also true when function accepts pointer, you simply can not pass a value to it.

When it comes to a method, that’s not the case. We can define a method with value or pointer receive and call it on pointer or value.

In the above program, we defined changeName method which accepts a pointer but we called on the value e which is legal because Go under the hood will pass a pointer of e to it. Also, we defined showSalary method which accepts value but we called on the pointer to e which is legal because Go under the hood will pass the value of the pointer to it.

We tried to change salary of e inside showSalary method but did not work as we can see from the result. This is because even we call this method on a pointer, Go sent only copy of the value to that method.

☛ Methods on non-struct type

So far we have seen methods on struct type but as from the definition of the method, it can accept any type as a receiver as long as type definition and method definition is in the same package. So far, we defined struct and method in the same main package, hence it worked.

To show this, we will try to add a method toUpperCase on the built-in type string.

From the above program, we created the method toUpperCase which accepts string as receiver type. Hence we expect string.toUpperCase() to work and return uppercase version of the receiver s. We used strings package to convert a string to uppercase.

But the above program will run into a compilation error

program.go:8: cannot define new methods on non-local type string
program.go:14: str.toUpperCase undefined (type string has no field or method toUpperCase)

This is because, type string and method toUpperCase is not defined in the same package. Let’s create a type alias MyString of string and make it work.

From the above program, we created the method toUpperCase which now accepts MyString type. We needed to modify the internals of this method to pass string type to strings.ToUpper function, but we get it.

Now we can call str.toUpperCase() because str is of type MyString as we used type conversion on the line no. 16 to convert from string type to MyString type.


RunGo

A place to find introductory Go Programming Language tutorials and learning resources. Like my other tutorials on Web Development, Run Go publication features important Go articles with deep dive into core of the language with examples and sample code.

Uday Hiwarale

Written by

IIT • Software Developer • India | Follow: github.com/thatisuday | Ask: thatisuday@gmail.com | World iza better place coz some still write on Medium for free :)

RunGo

RunGo

A place to find introductory Go Programming Language tutorials and learning resources. Like my other tutorials on Web Development, Run Go publication features important Go articles with deep dive into core of the language with examples and sample code.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade