Iman Tumorang
Iman Tumorang
Software Engineer - Writer - Open Source Enthusiast - Startup Enthusiast
Mar 22, 2019 5 min read

Today I Learned: Pass By Reference on Interface Parameter in Golang

A simple diary about a simple thing that I learned about Pass By Reference in Golang

thumbnail for this post

Back to the college days, I remember there are 2 ways to pass a parameter to a function. One passes by value, and the other one passes by reference. Both of these ways have a different concept and sometimes it brings confusion to the programmer. 

In simple, pass by value is we pass the parameter without a pointer to that origin address of the value. And pass by reference is we pass the parameter with a pointer to the given parameter.

In Golang, we can see these two in this example below.

func passByValue(item string){}
func passByReference(item *string){}

Pass By Reference and Pass By Value in Golang

Actually, there are already many examples of how to do pass by reference and pass by value in Golang that we can find on the Internet. 

So here, I will make it a simple example.

package main
import (
 "fmt"
)
func main() {
 item := ""
 passByValue(item)
 fmt.Println(item)
 passByReference(&item)
 fmt.Println(item)
}
func passByValue(item string) {
 item = "hello"
}
func passByReference(item *string) {
 *item = "world"
}

As you see, in the example above. That’s how I usually passing the parameters based on their types (by references or by value).

Pass By Reference on Interface Param in Golang

But, one day. I face a problem that I need to solve about pass a parameter by reference. Not like any ordinary one that I’ve ever made, this one using an interface at the parameter. So basically, this function accepts anything in interface{}, and fill the value based on the logic that happens inside that function. 

The function looks like this below.

func doSomethinWithThisParam(item interface{}){}

This function is simple, it only accepts an interface, and do something inside it. It won’t return any error, so it just to hydrate the item with a value in it.

So to solve this weird behavior, I tried to solve with trying it on my owns. Here below I will explain the steps how I solve it.

First Attempt: Pointer to Interface [Not Worked]

At first, I tried to like the non-interface{} does. I put a pointer in the interface. But it doesn’t work. 

package main
import (
 "fmt"
)
func main() {
 var item Student
 doSomethinWithThisParam(&item)
 fmt.Printf("%+v", item)
}
type Student struct {
 ID   string
 Name string
}
func doSomethinWithThisParam(item *interface{}) {
 *item = &Student{
  ID:   "124",
  Name: "Iman Tumorang",
 }
}

This one can’t be compiled, it throws the error.

cannot use &item (type *Student) as type *interface {} in argument to doSomethinWithThisParam:
	*interface {} is pointer to interface, not interface

Second Attempt: Directly Assign Value to Interface [Not Worked]

The second one, I try without a pointer to the interface, but instead, I assign the value directly to the given param.

func doSomethinWithThisParam(item interface{}) {
 item = &Student{
  ID:   "124",
  Name: "Iman Tumorang",
 }
}
// Print: {ID: Name:}

And after print the data, it still not worked. It prints the empty value. 

Third Attempt: Casting to Original Type and Assign the Value [Worked but…]

Later, after trying many things, I found a worked one. The parameter is still an interface{}, but instead of directly assign the value, at first I had to cast it back to the original’s type. At this time, it’s a bit tricky. And we must careful how to use it. See the difference here below.

Not worked one
This one example below is not worked.

func doSomethinWithThisParam(item interface{}) {
 origin := item.(*Student)
 origin = &Student{
  ID:   "124",
  Name: "Iman Tumorang",
 }
 item = origin
}
// Print: {ID: Name:}

Worked one
But this one is worked. 

func doSomethinWithThisParam(item interface{}) {
 origin := item.(*Student)
 origin.Name = "Iman Tumorang"
 origin.ID = "124"
}
// Print: {ID:124 Name:Iman Tumorang}

Seriously????? 😱
At first, I’m a bit confused. What is really happening here? How could be when I made like this one it doesn’t work.

origin := item.(*Student)
 origin = &Student{
  ID:   "124",
  Name: "Iman Tumorang",
 }

But with this one, it worked.

origin := item.(*Student)
origin.Name = "Iman Tumorang"

Need a few minutes to figure this out. But later I understand why this happens.

Another worked one
After figuring the problem, I realized something. The first one is failed since it replaces the address. So instead to replace the address, I try a new approachment that only change the value.

func doSomethinWithThisParam(item interface{}) {
 origin := item.(*Student)
 *origin = Student{
  ID:   "124",
  Name: "Iman Tumorang",
 }
 item = origin
}
// Print: {ID:124 Name:Iman Tumorang}

This one is worked well. That made me realized that when we want to change the value in the pointer variable, we need to set directly to the value, not to change address it self.

Final Resolver
So after experimenting with many trials, finally I choose the last one. And because this function that I’m working on will a bit generic based on my current task, I transform it and add switch case condition so it will be more generic based on the switch case I made.

In simple, All my works on my current task can be described in this example below. There is a generic function that will accept interface{} and do something inside it. And it supports many structs. 

The snippet code would be like this:

Conclusions

This one is really-really a serious thing in Golang. We must very careful when working with pass-by-reference and interface{}. To avoid any unnecessary bugs, I recommend adding a unit test to each function that uses the pass-by-reference method.

To be honest, I’m stuck for an hour on this issue. So if you think this is a good thing to knows, kindly share this article so anyone won’t fall to the same problems.


comments powered by Disqus