Go语言 在func返回期间返回和修改基元值是否安全

slmsl1lt  于 2023-02-17  发布在  Go
关注(0)|答案(2)|浏览(152)

(根据讨论改写问题,原始问题如下)
Go语言规范给出了一些例子,说明在对切片和Map赋值时,原语的求值顺序对于函数调用是不确定的,但是没有一个例子提到多值返回,所以我不确定它们是否适用于这种情况。
规范中的示例

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified

使用规范的语言,this functions返回是否也未指定:

func run() (int, int) {
    a := 1
    f := func() int { a++; return a }
    return a, f() // always return 1,2 OR always returns 2,2 OR can return either?
}

如果没有指定计算顺序,那么像下面这样的非玩具示例可能会在将来某个时候编译器更新时中断:

func CountRows(ctx context.Context, db *pgxpool.Pool) (int, error) {
  row := db.QueryRow(ctx, "SELECT COUNT(*)")
  var count int
  return count, row.Scan(&count)
}

原始问题

我不清楚go语言的规范是否明确了从函数返回的值是一次返回一个还是在所有表达式都求值之后才返回。
Aka是this code保证总是输出10 <nil>(就像它在操场上做的那样)还是它可以输出0 <nil>

package main

import "fmt"

func main() {
    fmt.Println(run())
    // Output: 10 <nil>
}

func run() (int, error) {
    var i int
    return i, inc(&i)
}

func inc(i *int) error {
    *i = *i + 10
    return nil
}

编辑

This相关问题表明规范中未指定基元返回值的求值顺序

hpcdzsge

hpcdzsge1#

1.“此函数返回值也未指定吗?”
是的。正如您所发现的,语言指定了某些事情的求值顺序:
当计算表达式、赋值语句或返回语句的操作数时,所有函数调用方法调用通信操作都按词法从左到右的顺序计算。
当然,对于表达式、赋值语句或返回语句中的求值顺序,函数调用、方法调用或通信操作之外的任何操作都是未指定的。
1.“如果编译器更新,类似下面的非玩具示例可能在将来某个时间中断”
是的,即使编译器没有更新,如果你期望它有任何依赖于求值顺序的特定结果,那么代码已经被破坏了,因为语言并没有说代码会做你认为它会做的事情。
1.是从函数返回的值,每次“返回”一个或在计算完所有表达式后返回一个。
不需要作出这种区分。
1.“此代码是否保证始终输出10 <nil>(就像它在操场上所做的那样),或者它是否可以输出0 <nil>?”
是的,它可以输出0, <nil>,这与前面的run示例基本相同,其中闭包f被重构为一个名为inc的函数。

qvtsj1bj

qvtsj1bj2#

是的!这真是个好问题。
编写此代码的另一种方法是

package main

import "fmt"

func main() {
    fmt.Println(run())
    // Output: 10 <nil>
}

func run() (int, error) {
    var i int
    err := inc(&i)
    return i, err
}

func inc(i *int) error {
    *i = *i + 10
    return nil
}

这与您可以执行以下操作的原因相同

func main() {
    callIt()
}

func callIt() (int, error) {
    return multiReturn()
}

func multiReturn() (int, error) {
    return 0, nil
}

此外,您也可以这样做,如果您将数据库事务(例如) Package 在错误处理程序中,这将非常有用

func main() {
    result, err := assignInReturn()
    if err != nil {
        panic(err)
    }

    // the result here, assuming no error occurred, will be 1...
    fmt.Println(result)
}

func assignInReturn() (int, error) {
    var i int
    return i, wrapErrFunc(func() error {
        // db call
        if err := dbCall(); err != nil {
            return err
        }

        i = 1 // ...because of this. This will set `i` to 1 before `assignInReturn` returns to its caller
        return nil
    })
}

func wrapErrFunc(fn func() error) error {
    return fn()
}

func dbCall() error {
    // db query
    return nil
}

在这些情况下,可以确定顶级return中的项将在返回给其调用者之前进行求值

相关问题