Call by value and call by reference are two different ways of passing arguments to a function.

Call By Value

Let’s understand what call by value is. In “call by value”, the argument value is copied into the function’s parameter. This means that any changes made to the parameter inside the function do not affect the original argument value outside the function. The original argument value is protected, and the function only operates on a copy of the value.


func callByValue(a, b int) (int, int) {

	temp := a
	
	a = b
	
	b = temp
	
	return a, b

}

Call By Reference

Let’s understand what call by reference is. In “call by reference”, instead of copying the argument value into the function’s parameter, the parameter is a reference (or a pointer) to the original argument. This means that any changes made to the parameter inside the function will affect the original argument value outside the function. The original argument value is not protected and can be modified by the function.

It’s worth noting that “call by reference” can have different variations, such as “pass by reference” and “pass by pointer”. In “pass by reference”, the reference to the argument is passed directly, while in “pass by pointer”, the pointer to the argument is passed. These variations are similar to each other and are often used interchangeably.


func callByRef(c, d *int) (int, int) {

	temp := *c
	
	*c = *d
	
	*d = temp
	
	return *c, *d

}

complete code

package main

import (
	"fmt"
)

func main() {
	fmt.Println("\n=========================")
	fmt.Println("Call By Value Code Starts")
	fmt.Println("=========================")
	x, y := 10, 20

	fmt.Printf("Before swap: x = %d, y = %d\n", x, y)
	x1, y1 := callByValue(x, y)
	fmt.Printf("In function the changed values of x is: %d & y is: %d \n", x1, y1)
	fmt.Printf("After swap: x = %d, y = %d\n", x, y)
	fmt.Println("\n=========================")
	fmt.Println("Call By Value Code Ends")
	fmt.Println("=========================")

	fmt.Println("\n=============================")
	fmt.Println("Call By Reference Code Starts")
	fmt.Println("=============================")
	u, v := 10, 20

	fmt.Printf("Before swap: u = %d, v = %d\n", u, v)
	u1, v1 := callByRef(&u, &v)
	fmt.Printf("In function the changed values of x is: %d & y is: %d \n", u1, v1)
	fmt.Printf("After swap: u = %d, v = %d\n", u, v)
	fmt.Println("\n=============================")
	fmt.Println("Call By Value Reference Ends")
	fmt.Println("=============================")
}

func callByValue(a, b int) (int, int) {
	temp := a
	a = b
	b = temp
	return a, b
}

func callByRef(c, d *int) (int, int) {
	temp := *c
	*c = *d
	*d = temp
	return *c, *d
}

Result

=========================
Call By Value Code Starts
=========================

Before swap: x = 10, y = 20
In function the changed values of x is: 20 & y is: 10 
After swap: x = 10, y = 20

=========================
Call By Value Code Ends
=========================

=============================
Call By Reference Code Starts
=============================

Before swap: u = 10, v = 20
In function the changed values of x is: 20 & y is: 10 
After swap: u = 20, v = 10

=============================
Call By Value Reference Ends
=============================

Now let’s understand why we get confused about pass by reference in go

As mentioned above pass by reference & call by reference are most of the times used interchangably.

In general, both terms refer to a way of passing arguments to a function that allows the function to modify the original value of the argument. However, the precise mechanism by which this is achieved can vary between programming languages and implementations.

  • Pass by reference: In this mechanism, a reference or pointer to the original value of the argument is passed to the function. The function can then use this reference or pointer to modify the original value of the argument. This is typically done in languages that support pointers or references, such as C++ or Java.

  • Call by reference: In this mechanism, the address of the original value of the argument is passed to the function. The function can then use this address to modify the original value of the argument. This is a specific implementation of pass by reference, and is used in some languages, such as Pascal.

Go does not have the concept of “reference variables” in the same way that some other programming languages, such as C++ or Java, do.

In Go, all values are passed by value. When you pass a variable to a function or assign it to another variable, a copy of its value is made. This means that changing the value of the copy does not affect the original variable.

However, Go does have pointers, which are variables that store the memory address of another variable. You can think of a pointer as a “reference” to another variable, but it’s important to note that pointers themselves are variables, not references.

In Go, you can declare a pointer using the * symbol before the type name, like this:

var x int = 42
var p *int = &x

p here is a pointer to an integer, and it is initialized with the address of the variable x. You can then use the * operator to access the value that p points to, like this:

fmt.Println(*p) // 42

You can also modify the value that p points to by dereferencing it with the * operator and assigning a new value, like this:

*p = 123
fmt.Println(x) // 123

So while Go does not have “reference variables” in the same sense as some other languages, you can use pointers to achieve similar functionality.

See the above Call by Referenc section for this

Go or Python, don’t have a direct equivalent to pass by reference or call by reference. Instead, they may use other mechanisms, such as pass by value with pointers or pass by object reference, to achieve similar functionality.


Interestingly Go map & channels are reference types

A reference type is a type whose value is represented by a memory address. When you assign a reference type to a variable or pass it as an argument to a function, you are actually passing a reference to the memory location where the value is stored, rather than a copy of the value itself.

In the case of maps and channels, this means that if you assign a map or channel to a new variable or pass it as an argument to a function, the new variable or function parameter will point to the same underlying data structure as the original map or channel. This can be useful for sharing data between different parts of your program or for passing large data structures between functions without incurring the overhead of copying the data.

package main

import (
	"fmt"
)

func main() {
	m := make(map[string]int)
	m["a"] = 1
	m["b"] = 2
	fmt.Println(m) // map[a:1 b:2]
	m2 := m
	m2["c"] = 3
	fmt.Println(m2) // map[a:1 b:2 c:3]
	fmt.Println(m)  // map[a:1 b:2 c:3]
}

In this example, m is a map that contains two key-value pairs. We then assign m to a new variable m2, and modify m2 by adding a new key-value pair. Because m and m2 both refer to the same underlying data structure, the modification to m2 also affects m.

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)

    go func() {
        ch <- 42
    }()

    fmt.Println(<-ch) // Output: 42

    anotherCh := ch // reference to the same channel

    go func() {
        anotherCh <- 100
    }()

    fmt.Println(<-ch) // Output: 100
}

In this example, we create a channel ch of type int using the make function. We then launch a goroutine that sends the value 42 to the channel ch. We use the channel operator <- to receive the value from the channel and print it.

We then create another variable anotherCh and assign it to ch. This means that anotherCh now refers to the same channel as ch.

We launch another goroutine that sends the value 100 to anotherCh. We then use the channel operator <- to receive the value from ch (not anotherCh) and print it. Since both ch and anotherCh refer to the same channel, the value sent to anotherCh is received from ch.

It’s worth noting that the direction of the arrow in the channel operator indicates the direction of the data flow. When the arrow points towards the channel variable, it means that data is being sent to the channel. When the arrow points away from the channel variable, it means that data is being received from the channel.

fmt.Println(anotherCh) //0xc000020180
fmt.Println(ch)        //0xc000020180

without the arrow(<-) operator it prints the address of the variables & surprise surprise, they are same.