One easy way to understand is injecting debug messages before and after lines. Consider the following modified codes:
package main
import (
"fmt"
)
func liner(functionName string, format string, a ...interface{}) {
info := fmt.Sprintf(format, a...)
fmt.Printf("[%s] %v\n", functionName, info)
}
func main() {
liner("main", "debug line before define x")
x := 2
liner("main", "debug line before calling foo, x=%v", x)
foo(x)
liner("main", "debug line after foo, x=%v", x)
fmt.Println(x)
liner("main", "debug line after foo, after print, x=%v", x)
}
func foo(y int) {
liner("foo", "debug line enters foo. y=%v", y)
fmt.Println(y)
liner("foo", "debug line, after print y, y=%v", y)
y = 43
liner("foo", "debug line, after set y, y=%v", y)
fmt.Println(y)
liner("foo", "debug line, end foo, y=%v", y)
}
Output with mixed-up liner:
[main] debug line before define x
[main] debug line before calling foo, x=2
[foo] debug line enters foo. y=2
2
[foo] debug line, after print y, y=2
[foo] debug line, after set y, y=43
43
[foo] debug line, end foo, y=43
[main] debug line after foo, x=2
2
[main] debug line after foo, after print, x=2
The main function is indicated by [main] tag while foo function indicated by [foo] tag.
Notice that at the first line of [foo] tag after the cpu enters the function, y is the same as x instead of 0. This is what it meant by your lecturer, the value was passed in from x to y.
Due to the nature of “Passed by Value”, when [main] resumes back after [foo] exited, x is still back to 2 instead of 43, like how you modified it inside foo function. This essentially means that the function clones the value into y instead of modifying x directly.
NOTE:
You can only do this liner experiment when not dealing with concurrency. Otherwise, it’s pointless
and incomprehensible. You need a different tool for that.