Golang pass by value VS pass by reference [Explained]


GO

Author: Tuan Nguyen
Reviewer: Deepak Prasad

When working with programming languages like Java, C#, C/C++, Go, etc. that allow us to access a pointer value, we must be careful to distinguish between passing by value and passing by reference.

  • Passing by value: The value of a function argument is copied to another location in the memory when pass-by-value is used. Within the function, only the copy of the variable is accessed or modified. As a result, the original value is unaffected.
  • Passing by reference: The memory address is provided to that function when using pass-by-reference. The function, in other words, has access to the real variable.

In conclusion, pass-by-value copies the value, but pass-by-reference provides the memory location.

Golang pass by value VS pass by reference [Explained]

 

Golang pass by value and pass by reference

Using with basic data type

Here is an example of how to pass-by-value and pass-by-reference basic data types (int64, string,...) to a function

package main

import "fmt"

func main() {
	a, b := 1, 2
	c, d := 3, 4

	// Initialize Value
	fmt.Printf("Value a: %d, b: %d, c: %d, d: %d\n", a, b, c, d)
	fmt.Printf("Memory Address a: %p, b: %p, c: %p, d: %p\n", &a, &b, &c, &d)

	fmt.Println("----------")

	// Passing By Value
	Swap(a, b)

	// Passing By Reference
	SwapRef(&c, &d)

	fmt.Printf("Value a: %d, b: %d, c: %d, d: %d\n", a, b, c, d)
	fmt.Printf("Memory Address a: %p, b: %p, c: %p, d: %p\n", &a, &b, &c, &d)
}

// Pass By Value
func Swap(x, y int) {
	fmt.Printf("Swap parameter memory address: %p, %p\n", &x, &y)
	x, y = y, x
}

// Pass By Reference
func SwapRef(x, y *int) {
	fmt.Printf("Swap Reference parameter memory address: %p, %p\n", x, y)
	*x, *y = *y, *x
}

Output:

Value a: 1, b: 2, c: 3, d: 4
Memory Address a: 0xc000016078, b: 0xc000016090, c: 0xc000016098, d: 0xc0000160a0
----------
Swap parameter memory address: 0xc0000160a8, 0xc0000160d0
Swap Reference parameter memory address: 0xc000016098, 0xc0000160a0
Value a: 1, b: 2, c: 4, d: 3
Memory Address a: 0xc000016078, b: 0xc000016090, c: 0xc000016098, d: 0xc0000160a0

In the above example, we have 2 functions:

  • Swap(x,y int) takes 2 integers as the parameter. We can see that the memory location of the values are not the same as a, b from the main() because Go copy the value of a, b and initialize new memory locations. The final output is we can not swap 2 value a, b because it is Pass by value.
  • SwapRef(x,y  *int) takes 2 pointers integer as the parameter. The memory locations of the values are the same as c, d so everything that we do to x, y inside SwapRefwill affect c, d values. The final output will swap c and d  because of Pass by reference.

Other basic data types like int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, string, bool, byte, rune, Array, Structs . Array and Struct have the same property with basic data types

 

Using with Referenced Data Type 

In the example below, we will take slice and map as parameter and see if we could change the content of those parameters:

package main

import (
	"fmt"
)

func main() {
	// Slices
	fmt.Println("Slice as parameter")
	var arrInt []int = []int{2, 4, 6, 7, 10, 12}
	var sliceInt = arrInt[3:]

	fmt.Printf("ArrInt: %+v, SliceInt: %+v\n", arrInt, sliceInt)

	ChangeSlice(sliceInt)
	fmt.Println("After append: ")
	fmt.Printf("ArrInt: %+v, SliceInt: %+v\n", arrInt, sliceInt)

	// Map
	fmt.Println("======================")
	fmt.Println("Map as parameter")
	var map1 = make(map[string]interface{})
	fmt.Printf("emptyMap : %+v\n", map1)

	ChangeMap(map1)
	fmt.Println("After add a key:")
	fmt.Printf("emptyMap : %+v\n", map1)
}

// add a key to map
func ChangeMap(val map[string]interface{}) {
	val["new key"] = 1000000
}

// change value
func ChangeSlice(slice []int) {
	slice[0] = 1000000
}

Output:

Slice as parameter
ArrInt: [2 4 6 7 10 12], SliceInt: [7 10 12]
After append:
ArrInt: [2 4 6 1000000 10 12], SliceInt: [1000000 10 12]
======================
Map as parameter
emptyMap : map[]
After add a key:
emptyMap : map[new key:1000000]

In the example above, slice from an array will have the array's memory location, so changing the value of the slice will affect the array value, and map data type is Pass by Reference by default, so changing anything inside the function will change the original map value. If we want to do a Pass by Value for map, we can use copy map, which will create a new variable of the map.

Noted that slice is a dynamically-sized, flexible view into the elements of an array. So in the example below, if we change the size of the slice, the array will no longer be affected:

package main

import (
	"fmt"
)

func main() {
	// Slices
	fmt.Println("Slice as parameter")
	var arrInt []int = []int{2, 4, 6, 7, 10, 12}
	var sliceInt = arrInt[3:]

	fmt.Printf("ArrInt: %+v, SliceInt: %+v\n", arrInt, sliceInt)

	ChangeSlice(sliceInt)
	fmt.Println("After append: ")
	fmt.Printf("ArrInt: %+v, SliceInt: %+v\n", arrInt, sliceInt)
}

// append value
func ChangeSlice(slice []int) {
	slice = append(slice, 10)
}

Output:

Slice as parameter
ArrInt: [2 4 6 7 10 12], SliceInt: [7 10 12]
After append:
ArrInt: [2 4 6 7 10 12], SliceInt: [7 10 12]

 

Using struct as parameters 

In the example below, we will write 2 functions to takes a struct and a pointer to struct as parameter:

package main

import "fmt"

type ExampleStruct struct {
	Greeting string
}

func (e ExampleStruct) Change() {
	fmt.Println("Pass by value:")
	fmt.Printf("Memory Location when calling func: %p\n", &e)
	e.Greeting = "Hello Pass By Value"
}

func (e *ExampleStruct) ChangeRef() {
	fmt.Println("Pass by ref:")
	fmt.Printf("Memory Location when calling func: %p\n", e)
	e.Greeting = "Hello Pass By Ref"
}

func main() {
	ex1 := ExampleStruct{
		Greeting: "Hello",
	}

	fmt.Println("Init:")
	fmt.Printf("Memory Location: %p\n", &ex1)
	fmt.Printf("Value: %+v\n", ex1)

	fmt.Println("-------")
	ex1.Change()
	fmt.Printf("Memory Location: %p\n", &ex1)
	fmt.Printf("Value: %+v\n", ex1)

	fmt.Println("-------")
	ex1.ChangeRef()
	fmt.Printf("Memory Location: %p\n", &ex1)
	fmt.Printf("Value: %+v\n", ex1)

}

Output:

Init:
Memory Location: 0xc000040250
Value: {Greeting:Hello}
-------
Pass by value:
Memory Location when calling func: 0xc000040270
Memory Location: 0xc000040250
Value: {Greeting:Hello}
-------
Pass by ref:
Memory Location when calling func: 0xc000040250
Memory Location: 0xc000040250
Value: {Greeting:Hello Pass By Ref}

 

Summary

This concludes this article; hopefully, it has helped you better understand Pass by Value vs Pass by Reference, particularly in the Go programming language. The main difference between pass by value and pass by reference is that, in pass by value, the parameter value copies to another variable while in pass by reference, the actual parameter passes to the function.

 

References

https://go.dev/tour/basics/11
https://go.dev/doc/effective_go

 

Tuan Nguyen

Tuan Nguyen

He is proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment