Pass Parameters by Reference in GolangA simple diary about a simple thing that I learned about Pass By Reference in Golangpass by reference on interface parameter in Golang
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 terms, pass by value is when we pass the parameter without a pointer to that origin address of the value. And pass by reference is when we pass the parameter with a pointer to the given parameter.
In Golang, we can see these two in this example below.
<code class="language-markup">func passByValue(item <strong>string</strong>){}
func passByReference(item <strong>*string</strong>){}</code>
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.
<code class="language-markup">package main</code>
<code class="language-markup">import (
"fmt"
)</code>
<code class="language-markup">func main() {
item := ""
passByValue(item)
fmt.Println(item)
passByReference(&item)
fmt.Println(item)
}</code>
<code class="language-markup">func passByValue(item string) {
item = "hello"
}</code>
<code class="language-markup">func passByReference(item *string) {
*item = "world"
}</code>
The above example shows a typical way we might pass the parameters based on their types (by references or by value).
But, one day. I faced a problem that I needed to solve about passing 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 fills the value based on the logic that happens inside that function.
The function looks like this below.
<code class="language-markup">func doSomethinWithThisParam(item interface{}){}</code>
This function is simple, it only accepts an interface, and does 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 own. Here below I will explain the steps how I solve it.
At first, I tried to like the non-interface{} does. I put a pointer in the interface. But it doesn’t work.
<code class="language-markup">package main</code>
<code class="language-markup">import (
"fmt"
)</code>
<code class="language-markup">func main() {
var item Student
doSomethinWithThisParam(&item)
fmt.Printf("%+v", item)
}</code>
<code class="language-markup">type Student struct {
ID string
Name string
}</code>
<code class="language-markup">func doSomethinWithThisParam(item *interface{}) {
*item = &Student{
ID: "124",
Name: "Iman Tumorang",
}
}</code>
This one can’t be compiled, it throws the error.
<code class="language-markup">cannot use &item (type *Student) as type *interface {} in argument to doSomethinWithThisParam:
*interface {} is pointer to interface, not interface</code>
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.
<code class="language-markup">func doSomethinWithThisParam(item interface{}) {
item = &Student{
ID: "124",
Name: "Iman Tumorang",
}
}</code>
<code class="language-markup">// Print: {ID: Name:}</code>
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:
This one example below is not worked.
<code class="language-markup">func doSomethinWithThisParam(item interface{}) {
origin := item.(*Student)
origin = &Student{
ID: "124",
Name: "Iman Tumorang",
}
item = origin
}</code>
<code class="language-markup">// Print: {ID: Name:}</code>
Worked:
But this one is worked.
<code class="language-markup">func doSomethinWithThisParam(item interface{}) {
origin := item.(*Student)
origin.Name = "Iman Tumorang"
origin.ID = "124"
}</code>
<code class="language-markup">// Print: {ID:124 Name:Iman Tumorang}</code>
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.
<code class="language-markup">origin := item.(*Student)
origin = &Student{
ID: "124",
Name: "Iman Tumorang",
}</code>
But with this one, it worked.
<code class="language-markup">origin := item.(*Student)
origin.Name = "Iman Tumorang"</code>
Need a few minutes to figure this out. But later I understand why this happens.
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.
<code class="language-markup">func doSomethinWithThisParam(item interface{}) {
origin := item.(*Student)
<strong>*origin</strong> = Student{
ID: "124",
Name: "Iman Tumorang",
}
item = origin
}</code>
<code class="language-markup">// Print: {ID:124 Name:Iman Tumorang}</code>
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.
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:
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{}) {
switch v := item.(type) {
case *Student:
*v = Student{
ID: "124",
Name: "Iman Tumorang",
}
// another case
}
}
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.