-4

I'm still new to programming. Forgive my lack of computer science knowledge. Not sure if this question is specific to Golang or computer science in general...

I always thought that functions do not alter variables/data held outside their own scope unless you use a return statement back into the other scope, or unless they are higher in the hierarchy of scopes. One may argue that functions f1 and f2 in this example are called from a lower scope. However, this still doesn't explain why I'm getting different results for variable num and nums.

package main

import "fmt"

func f1(a int) {
    a = 50 // this will not work, as it shouldn't
}

func f2(a ...int) {
    a[0] = 50 // this will work without return statement
    a[1] = 50 // this will work without return statement
}

func main() {

    num := 2
    nums := []int{2, 2}

    f1(num)
    f2(nums...)

    fmt.Printf("function f1 doesn't affect the variable num and stays: %v\n", num)
    fmt.Printf("function f2 affects the variable nums and results in: %v", nums)

Questions:

  1. Why doesn't f2 require a return statement to modify nums like num would within f1?
  2. Golang functions are said to pass values (rather than reference), shouldn't that force the function to return copies?
  3. Can this happen in other languages? (I think I may have seen this in other languages).

3 Answers3

1

In go, function arguments are passed by value. That means, if you pass an int (like in f1), compiler will pass the value of f1, essentially copying it. If the function takes a *int and you pass &num, then the compiler passes the value of &num, which is a pointer to num. When the function changes *num, the value of the variable outside the function will change. If the function changes num, the pointer value of num will change, and it will point to a different variable.

As a contrast, Java passes all primitive values as value, and all objects by reference. That is, if you pass an int, there is no way for the function to modify the value of that int that is visible to the caller. If you want to pass an int the function can modify, you put that in a class and pass an instance of that class in Java.

A slice (as in f2) contains a pointer to the underlying array. When you call a function with a slice, the slice header (containing a pointer to the underlying array) is copied, so when the function changes the slice elements, the underlying array elements change.

The question of scope is somewhat different. Scope of a function is all the variables it can see. Those are the global variables (if from different packages, exported global variables), function arguments, and if the function is declared nested within another function, all the variables visible in that function at that point.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
  • I wasn't asking about the slicing reference, but about how the function works instead. And I think this might be the best answer as the second paragraph explains best (so far) why the function affects the slice even though it doesn't return anything. It has to do with how functions access memory. Does this also have anything to do with first-class citizen? –  Jun 30 '20 at 00:40
  • A function doesn't need to return something to affect places in memory otherwise invisible to it. If you pass a pointer to a function, then the function can assign values to that pointer to change wherever the pointer is pointing to. It may be pointing to variables the function otherwise doesn't have access. – Burak Serdar Jun 30 '20 at 04:04
  • ok.... I see, because in your first paragraph, you explained that functionality happens only if the function "takes" *int and "passes" &num. I thought this meant that the function parameter needs to have *int. But it appears that the pointer from the num variable passed into it is sufficient to cause this effect without changing the function's syntax. Is this correct? –  Jun 30 '20 at 04:52
  • No. Function f gets *int argument, the function calling f passed &num. You cannot pass a pointer to a function that isn't declared to get a pointer. That is: `func f(i *int)`, and you call `f` as: `f(&num)`, then `f` can change the value of `num`. In your original question: a slice contains a pointer, so the function can change the slice contents. – Burak Serdar Jun 30 '20 at 05:02
1

This is the correct behaviour, since a ...int is equal to a slice e.g.: a []int

func f2(a []int) {
    a[0] = 50
    a[1] = 50
}
func main() {
    b := []int{2, 2}
    f2(b)
    fmt.Println(b) // [50 50]
}

And a slice is a view to the original data, here 'b'.

"Why doesn't f2 require a return statement to modify nums like num would in f1?"

In f2 you are using the slice, which has a pointer to the original array, so f2 can change the outside array.

"Golang functions are said to pass values (not reference), shouldn't that force to return copies? (If the question is related...)"

In f2 the slice itself is passed by value, meaning pointer and length and capacity of the original array.

"Can this happen in other languages? (I think I may have seen this in other langues)"

Too broad to answer, there are many languages and in general if you have a pointer to the outside world array, yes.


Edit:

package main

import "fmt"

func sum(a ...int) int {
    s := 0
    for _, v := range a {
        s += v
    }
    return s
}
func f2(a []int) {
    c := make([]int, len(a))
    copy(c, a)
    c[0] = 50
    fmt.Println(sum(c...)) // 52
}
func main() {
    b := []int{2, 2}
    fmt.Println(sum(1, 2, 3, 4)) // 10
    fmt.Println(sum(b...))       // 4

    f2(b)
    fmt.Println(b) // [2 2]
}

Notes:
The sum() function above is a pure function, since it has no side effect.
The new f2 function above is a pure function, since it has no side effect: it makes a copy of a into c then calls the sum.

wasmup
  • 14,541
  • 6
  • 42
  • 58
  • Thank you for answering. I understand that passing by reference simply means that it's making whatever surrounding code affect the local variable. However, I would think that a function without a return statement would not send back any transformation to affect that local code. The return statement could be used to dictate if the function overrides references or not. Not sure why it's designed that way. I don't know much about pointers, so I'm still digesting the other answer. However you put it in simple terms. Thank you. –  Jun 29 '20 at 20:48
  • 1
    To be precise this `f2` function is not a [pure function](https://en.wikipedia.org/wiki/Pure_function), since it has a side effect. and the philosophy of a slice is to use it this way and slice, I think is the most important data structure in Go, so you need to understand it this way, and this is by design. I hope this helps. – wasmup Jun 30 '20 at 01:04
  • Thank you very much! I just discovered that some functions cannot be pure. For some reason my brain thinks only in pure functional programming, so I didn't know that. One last question pls before I mark this as the answer... This may also help others. Are you saying the function is unpure due to the way I coded it, or because the pointer made it so? –  Jun 30 '20 at 04:25
  • in other words, is there a way to prevent this in the way I write the function (without touching the slice) ? –  Jun 30 '20 at 04:46
  • Yes, it is your choice, to make the function pure: You may copy the original slice, see the edit. – wasmup Jun 30 '20 at 05:01
  • Even though I understand this is not technically a "Pure" function (within a compiler) and I could have copied the slice outside the function, I'm marking this as the answer since it answers how the function behaves rather than how slices behave and most of my 3 questions were about the return statement of the function or why the function behaves this way. It also gave me insight in the term "pure function" that I didn't know about, but yet naturally thought that way. –  Jul 01 '20 at 15:16
1

1 & 2) Both questions can be answered when looking at how slices work in Go. There's a blog article on it.

In general, all variables are passed by value in Go. You can use pointers (e.g. *int for f1) to pass by reference (or more correct, the address of the pointer). However, slices are technically also passed by value.

When we look here, we can get an idea how they work:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Len and Cap are integers, but Data is a pointer.

When this struct is copied (when passing by value), a copy of Len, Cap and Data will be made. Since Data is a pointer, any modifications made to the value it's pointing to will be visible after your function returns.

You can also read this

xarantolus
  • 1,961
  • 1
  • 11
  • 19
  • Thanks for answering. What I was trying to understand is why the function is returning a value in the absence of a return statement. You somewhat explain it with the struct example by making me understand that it's not the function the problem. However, I still don't understand why use pointers designed that way and how/why it would be used this way. Seems risky as it creates anti-patterns. What is the advantage? –  Jun 29 '20 at 21:20
  • @Alex, the function does not return anything. Explaining the history of pointers (as pointers in Go are not special in any way) is a little too broad for a SO answer. – JimB Jun 29 '20 at 22:25
  • @JimB Thanks for replying. I know the function doesn't return anything and I understand how a slice is a reference. What I am asking is why the function doesn't block the reference to the slice since it doesn't have a return statement. What is it doing returning something? You even said it yourself, it shouldn't return anything. And I think it has something to do with how function accesses the memory. The function seems to act like everything else that's not a function. –  Jun 30 '20 at 00:25
  • @Alex, sorry, I don’t understand what you mean. You say you understand that the function does not return anything, then you go on to ask “What is it doing returning something?”. There are no returned values, period. I think you need to do some reading on what a pointer actually is, and why they are used. While it’s not aN acceptable question type here any longer, perhaps this old post can help: https://stackoverflow.com/questions/5727/what-are-the-barriers-to-understanding-pointers-and-what-can-be-done-to-overcome – JimB Jun 30 '20 at 02:59
  • Yeah, you are right that I don't know enough about pointers. It appears to me like pointers render a function completely unpure and throws "functional programming" completely outside the window. I'll look more into that. Thanks! –  Jun 30 '20 at 04:21
  • @Alex If the slice was a house, then the pointer to it is piece of paper with the address written on it. When passing this paper (or any copy of it) to somebody else, they will know where the house is located. If they change anything at the house that has the address that is written on *their* piece of paper, they will modify the house the original paper points to (as they are the same). To prevent this, one can `make` a new slice/house and [`copy`](https://pkg.go.dev/builtin#copy) everything from the old one to it. Any modifications to *that* slice/house will *not* show up at the original one – xarantolus Jun 30 '20 at 12:21
  • @Alex: well of course functions with side effects are not pure, and do not fall into the category of pure functional programming. Go does not have constructs to enforce pure functions, so I don't understand the point of this comparison. – JimB Jun 30 '20 at 14:47
  • You've unintentionally given the answer even though you say you don't understand. The answer is: "Go does not have constructs to enforce pure functions". I think others beat you to it. But thank you for taking the time to answer. –  Jul 01 '20 at 15:04