What is the evaluation order of function arguments? - go

I'm using this version of Go:
$ go version
go version go1.18 windows/amd64
The results are different when struct A has only one field and B has two or above fields, and it just happens when parameter type is interface.
I'm not sure if that's a bug:
package main
import (
"fmt"
)
func main() {
a := A{}
m("A", a, SetAI(&a))
b := B{}
m("B", b, SetBI(&b))
}
type A struct {
I int
S string
}
type B struct {
I int
}
func SetAI(a *A) A {
a.I = 10
return *a
}
func SetBI(b *B) B {
b.I = 10
return *b
}
func m(name string, arg1, arg2 interface{}) {
fmt.Println(name+":", arg1, arg2)
}
I expected this output:
A: {10} {10}
B: {10} {10}
Instead I got this:
A: {0 } {10 }
B: {10} {10}

The source of confusion and different output is the order of evaluation of the arguments.
Look at your example:
m("A", a, SetAI(&a))
This is a function call, the function value and arguments are evaluated in the usual order:
Otherwise, when evaluating the operands of an expression, assignment, or return statement, all function calls, method calls, and communication operations are evaluated in lexical left-to-right order.
For example, in the (function-local) assignment
y[f()], ok = g(h(), i()+x[j()], <-c), k()
the function calls and communication happen in the order f(), h(), i(), j(), <-c, g(), and k(). However, the order of those events compared to the evaluation and indexing of x and the evaluation of y is not specified.
So basically the spec only guarantees that function calls and communication
ops happen from left-to-right.
Your call has arguments "A", a and SetAI(&a). There is no guarantee if the second argument a is evaluated before the &a param passed to SetAI(), and this very much matters because SetAI() modifies a. Since the order is not guaranteed, you can't rely on which will be evaluated first, both order is valid by the spec.
If you make the evaluation explicit by doing a copy of the struct before, you get the same result:
a := A{}
aCopy := a
m("A", aCopy, SetAI(&a))
b := B{}
bCopy := b
m("B", bCopy, SetBI(&b))
This will output (try it on the Go Playground):
A: {0 } {10 }
B: {0} {10}
Or if you want the function call to be evaluated first:
a := A{}
ap := SetAI(&a)
m("A", a, ap)
b := B{}
bp := SetBI(&b)
m("B", b, bp)
This will output 10 for each cases (try this one on the Go Playground):
A: {10 } {10 }
B: {10} {10}

Related

What's the difference between a generic slice argument and an argument constrained to slice types?

Consider the experimental package slices. The package is experimental, so I understand the signatures may change; I'm using it to illustrate the issue.
Consider the signatures of two functions from this package, slices.Contains and slices.Grow:
func Contains[E comparable](s []E, v E) bool
func Grow[S ~[]E, E any](s S, n int) S
The first argument to Contains has type []E (slice of Es) with E constrained by comparable (types that are comparable).
The first argument to Grow instead has type S (just S), with S constrained by ~[]E (types whose underlying type is a slice of E)
However it looks like there isn't any practical difference between what operations are allowed inside functions with such type params. If we declare some fake funcs with the same type parameters, we can see that both compile just fine:
As expected, in both functions we can len/cap, append, range, allocate with make, and index with [ ].
func fakeContains[E comparable](s []E, v E) {
fmt.Println(len(s), cap(s))
var e E
fmt.Println(append(s, e))
fmt.Println(make([]E, 4))
for _, x := range s {
fmt.Println(x)
}
fmt.Println(s[0])
fmt.Println(reflect.TypeOf(s).Kind())
}
func fakeGrow[S ~[]E, E any](s S, n int) {
fmt.Println(len(s), cap(s))
var e E
fmt.Println(append(s, e))
fmt.Println(make(S, 4))
for _, x := range s {
fmt.Println(x)
}
fmt.Println(s[0])
fmt.Println(reflect.TypeOf(s).Kind())
}
Even reflect.TypeOf(s).Kind() gives reflect.Slice in all cases.
The functions can also be tested with different types, and all compile:
// compiles just fine
func main() {
type MyUint64 uint64
type MyUint64Slice []uint64
foo := []uint64{0, 1, 2}
fakeContains(foo, 0)
fakeGrow(foo, 5)
bar := []MyUint64{3, 4, 5}
fakeContains(bar, 0)
fakeGrow(bar, 5)
baz := MyUint64Slice{6, 7, 8}
fakeContains(baz, 0)
fakeGrow(baz, 5)
}
The only actual difference in my understanding is that in slices.Grow the argument s S is not a slice. It's just constrained to slice types. And as a matter of fact reflect.TypeOf(s) gives a different output when the arg is an instance of type MyUint64Slice []uint64:
Contains with arg s []E gives reflect.TypeOf(s) -> []uint64
Grow with arg s S gives reflect.TypeOf(s) -> main.MyUint64Slice
However it's not immediately apparent to me what's the practical difference between the two.
Playground with the code: https://gotipplay.golang.org/p/zg2dGtSJwuI
Question
Are these two declarations equivalent in practice? If not, when should I choose one over the other?
It matters if you have to return a slice of the same (possibly named) type as the argument.
If you do not have to return a slice (just some other info e.g. a bool to report if the value is contained), you do not need to use a type parameter that itself constraints to a slice, you may use a type parameter for the element only.
If you have to return a slice of the same type as the input, you must use a type parameter that itself constraints to a slice (e.g. ~[]E).
To demonstrate, let's see these 2 implementations of Grow():
func Grow[S ~[]E, E any](s S, n int) S {
return append(s, make(S, n)...)[:len(s)]
}
func Grow2[E any](s []E, n int) []E {
return append(s, make([]E, n)...)[:len(s)]
}
If you pass a slice of a custom type having a slice as its underlying type, Grow() can return a value of the same type. Grow2() cannot: it can only return a value of an unnamed slice type: []E.
And the demonstration:
x := []int{1}
x2 := Grow(x, 10)
fmt.Printf("x2 %T len=%d cap=%d\n", x2, len(x2), cap(x2))
x3 := Grow2(x, 10)
fmt.Printf("x3 %T len=%d cap=%d\n", x3, len(x3), cap(x3))
type ints []int
y := ints{1}
y2 := Grow(y, 10)
fmt.Printf("y2 %T len=%d cap=%d\n", y2, len(y2), cap(y2))
y3 := Grow2(y, 10)
fmt.Printf("y3 %T len=%d cap=%d\n", y3, len(y3), cap(y3))
Output (try it on the Go Playground):
x2 []int len=1 cap=12
x3 []int len=1 cap=12
y2 main.ints len=1 cap=12
y3 []int len=1 cap=12
As you can see Grow2(y, 10) receives a value of type main.ints and yet it returns a value of type []int. This is not what we would want from it.

Golang unpack variadic arguments

How to unpack a variadic argument by ... in golang:
func run(a, b string, list ...int) {
}
func call(list ...int) {
run(x, y, list...) // use "list..." is not allowed
}
func main() {
a := []int{1, 2, 3}
call(a...)
}
Why this is illegal? As i have known, the list argument is a slice which can be unpacked by ....
unpack variadic arguments
For your example,
package main
import "fmt"
func run(a, b string, list ...int) {
fmt.Println(list)
for i, e := range list {
fmt.Println(i, e)
}
}
func call(list ...int) {
fmt.Println(list)
var x, y string
run(x, y, list...) // use "list..." is allowed
}
func main() {
a := []int{1, 2, 3}
fmt.Println(a)
call(a...)
}
Playground: https://play.golang.org/p/NI0L-nVVUxm
Output:
[1 2 3]
[1 2 3]
[1 2 3]
0 1
1 2
2 3
Variadic arguments can only be expanded if the slice you're passing to the variadic function is taking the place of the variadic argument. You cannot combine individual arguments and an expanded slice in a single variadic argument for this reason. When you use expansion, the function receives the "expanded" slice itself as its variadic parameter - it's semantic sugar that can be confusing, because these calls are not equivalent:
foo := []string{"alpha","beta"}
bar(foo...) // bar receives the foo slice as its variadic parameter
bar(foo[0], foo[1]) // bar receives a new slice with these elements copied to it
You can find more details in the spec, though the example they use makes some of the explanation unclear, because their example variadic function has multiple parameters. The section beginning "If the final argument is assignable" refers to the "final argument" not in a generic "final argument to a variadic function" sense, but in a specific "final argument to the function in the example" sense.
you didn't declare variables x and y. Create them or just use quotes.
https://play.golang.org/p/gZnMALrM4Uh
package main
import (
"fmt"
)
func run(a, b string, list ...int) {
fmt.Println(list)
}
func call(list ...int) {
run("x", "y", list...) // ok
}
func main() {
a := []int{1, 2, 3}
call(a...)
}

swap function not working in golang

Actually i just start to learn golang . In the beginning i think that = and := are same . But then i understand that there is some difference between this two .
I learned swap function in golnag
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
But when i rewrite this function using var this is not working
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
var a, b string
a ="hello"
b="world"
swap(a, b)
fmt.Println(a, b)
}
what is the error in this program ?
Another solution is to use pointers:
package main
import "fmt"
func swap(x, y *string) {
*x, *y = *y, *x
}
func main() {
var a, b string
a ="hello"
b="world"
swap(&a, &b)
fmt.Println(a, b)
}
https://play.golang.org/p/-vxUMlaVmN
The reason is that in second case values returned from swap are ignored. SO nothing is changed.
Try: https://play.golang.org/p/uADEf5X15g
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
var a, b string
a = "hello"
b = "world"
a, b = swap(a, b) //// <----
fmt.Println(a, b)
}
To respond your initial question, you should assign the values returned by swap to a and b like so
a, b = swap(b, a)
Notice that this is simple assignment , without the : attached to the equal
also, instead of a swap function, you could just try inplace reassignment:
a, b = b, a
Variables declaration
var a string - declaration of a variable with null value
a := "spam" - declaration of a variable with a concrete value
func f(a, b string) (string, string) { - declaration of a function with value parameters. It means you have new variables with passed values as arguments each time you call a function.
func f(a, b *string) (*string, *string) { - declaration of a function with pointer arguments. In it's turn it means you have pointers to passed variables each time you call the function.
Also...
a := *string - declaration of a pointer variable.
*a - value of a pointer variable.
&a - pointer of a value
In-place swap
To swap in-place (without returning and reassigning) you should swap values between pointers.
func swap(a, b *string) {
*a, *b = *b, *a
}
p.s.
Take into account that strings is read-only slices of bytes. And slices are reference type it means that an array behind the sub-slices of a common array or slice is the same. It doesn't related to the question but should be considered in such cases.

Comparing the return values of methods with multiple return values

Considering the following example methods, each of which returns three integers:
func a() (int, int, int) {...}
func b() (int, int, int) {...}
I want to know if the return values of these methods are the same, something like this:
equal := a() == b()
However, this doesn't compile because the compiler is expecting a single value:
my_file.go:14: multiple-value a() in single-value context
my_file.go:14: multiple-value b() in single-value context
My current work around is to create a custom type with the same signature as the methods, and then create a third function to do the checking:
type multiReturnFunc func() (int, int, int)
func a() (int, int, int) {...}
func b() (int, int, int) {...}
func checkMultiReturns(a, b multiReturnFunc) bool {
a1, a2, a3 := a()
b1, b2, b3 := b()
return a1 == b1 && a2 == b2 && a3 == b3
}
...
equal := checkMultiReturns(a, b)
But I would like to have a more general solution.
With structs
You can slightly improve it by storing the result in values of a struct which are comparable:
type Result struct {
a, b, c int
}
And using it:
p, q := Result{}, Result{}
p.a, p.b, p.c = a()
q.a, q.b, q.c = b()
fmt.Println(p == q)
With arrays
Or you can use arrays (arrays are also comparable unlike slices), although this won't be shorter, but you don't need a new type for this:
x, y := [3]int{}, [3]int{}
x[0], x[1], x[2] = a()
y[0], y[1], y[2] = b()
fmt.Println(x == y)
General solution (using reflect)
A general solution may be constructed using the reflect package. This basically calls both functions, and compares all the result values. Error checks omitted!
func check(v1, v2 reflect.Value) bool {
r1 := v1.Call(nil)
r2 := v2.Call(nil)
if len(r1) != len(r2) {
return false
}
for i, a := range r1 {
if a.Interface() != r2[i].Interface() {
return false
}
}
return true
}
And using it:
fmt.Println(check(reflect.ValueOf(a), reflect.ValueOf(b)))
Try these on the Go Playground.
This might not be exactly what you want, but if instead of returning three unnamed ints you return structs, you can compare them directly. E.g.
type XYZ struct{ X, Y, Z int }
func f() XYZ { return XYZ{1, 2, 3} }
func g() XYZ { return XYZ{1, 2, 3} }
func main() {
fmt.Println(f() == g())
// Output:
// true
}
Playground: http://play.golang.org/p/zFXPqPjTtZ.

Golang Operator Overloading

I understand that golang does not provide operator overloading, as it believe that it is increasing the complexity.
So I want to implement that for structures directly.
package main
import "fmt"
type A struct {
value1 int
value2 int
}
func (a A) AddValue(v A) A {
a.value1 += v.value1
a.value2 += v.value2
return a
}
func main() {
x, z := A{1, 2}, A{1, 2}
y := A{3, 4}
x = x.AddValue(y)
z.value1 += y.value1
z.value2 += y.value2
fmt.Println(x)
fmt.Println(z)
}
https://play.golang.org/p/1U8omyF8-V
From the above code, the AddValue works as I want to. However, my only concern is that it is a pass by value and hence I have to return the newly added value everytime.
Is there any other better method, in order to avoid returning the summed up variable.
Yes, use pointer receiver:
func (a *A) AddValue(v A) {
a.value1 += v.value1
a.value2 += v.value2
}
By using a pointer receiver, the address of a value of type A will be passed, and therefore if you modify the pointed object, you don't have to return it, you will modify the "original" object and not a copy.
You could also simply name it Add(). And you could also make its argument a pointer (for consistency):
func (a *A) Add(v *A) {
a.value1 += v.value1
a.value2 += v.value2
}
And so using it:
x, y := &A{1, 2}, &A{3, 4}
x.Add(y)
fmt.Println(x) // Prints &{4 6}
Notes
Note that even though you now have a pointer receiver, you can still call your Add() method on non-pointer values if they are addressable, so for example the following also works:
a, b := A{1, 2}, A{3, 4}
a.Add(&b)
fmt.Println(a)
a.Add() is a shorthand for (&a).Add(). Try these on the Go Playground.

Resources