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 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 asa, 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 valuea, 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 asc, d
so everything that we do tox, y
insideSwapRef
will affectc, 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