英文:
What is the evaluation order of function arguments?
问题
我正在使用以下版本的Go:
$ go version
go version go1.18 windows/amd64
当结构体A
只有一个字段而B
有两个或更多字段时,且参数类型为接口时,结果会有所不同。
我不确定这是否是一个 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)
}
我期望的输出是:
A: {10} {10}
B: {10} {10}
但实际输出是:
A: {0 } {10 }
B: {10} {10}
英文:
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}
答案1
得分: 7
混淆和不同的输出源于参数的评估顺序。
看看你的例子:
m("A", a, SetAI(&a))
这是一个函数调用,函数值和参数按照通常的顺序进行评估:
> 否则,在评估表达式、赋值语句或返回语句的操作数时,所有函数调用、方法调用和通信操作按照从左到右的词法顺序进行评估。
> 例如,在(函数局部的)赋值语句中
>
> y[f()], ok = g(h(), i()+x[j()], <-c), k()
>
> 函数调用和通信操作按照顺序 f()
、h()
、i()
、j()
、<-c
、g()
和 k()
发生。然而,这些事件与 x
的评估和索引以及 y
的评估的顺序没有指定。
因此,规范只保证函数调用和通信操作按照从左到右的顺序发生。
你的调用有参数 "A"
、a
和 SetAI(&a)
。无法保证第二个参数 a
在传递给 SetAI()
的 &a
参数之前进行评估,这非常重要,因为 SetAI()
修改了 a
。由于顺序没有保证,你不能依赖于哪个参数先进行评估,两种顺序都是有效的。
如果在之前明确进行结构体的复制来进行评估,你会得到相同的结果:
a := A{}
aCopy := a
m("A", aCopy, SetAI(&a))
b := B{}
bCopy := b
m("B", bCopy, SetBI(&b))
这将输出(在 Go Playground 上尝试):
A: {0 } {10 }
B: {0} {10}
或者如果你希望函数调用先进行评估:
a := A{}
ap := SetAI(&a)
m("A", a, ap)
b := B{}
bp := SetBI(&b)
m("B", b, bp)
这将对每个情况输出 10
(在 Go Playground 上尝试):
A: {10 } {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}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论