(вопрос изменен на основе обсуждения, исходный вопрос ниже)
Спецификация языка Go дает примеры, показывающие, что порядок оценки примитивов не указан в отношении вызовов функций во время назначения срезов и карт. Однако ни в одном из примеров не упоминается многозначный возврат, поэтому я не уверен, применимы ли они в этом случае или нет.
Примеры из спецификации
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
Используя язык спецификации, эта функция возвращает также неуказанный:
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 определяет, «возвращаются» ли значения, возвращаемые из funcs, по одному или после оценки всех выражений.
Ака этот код гарантированно всегда выводит 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
}
Редактировать
Этот вопрос, связанный с, предполагает, что порядок оценки примитивного возвращаемого значения не указан в спецификации.
В моей голове компилятор мог интерпретировать возврат как ret1 := i; ret2 := inc(&i); return ret1, ret2, что привело бы к 0 <nil> и было неоднозначно для меня. В спецификации говорится, что порядок оценки примитивов не определен, и приводятся примеры структур и карт, но не операторы возврата, поэтому мне неясно, охватывает ли спецификация этот случай.
Вы запутываете свой вопрос, это гораздо более простой случай, показывающий порядок оценки go.dev/play/p/3m7dyhmqx-Y. Оценка отдельных выражений операндов происходит слева направо, и только затем вы оцениваете Println (или оператор return в вашем случае).
Я не понимаю, чем ваш пример отличается от "неуказанных" примеров в спецификациях go.dev/play/p/NG-mBzlZFLc - конкретно я не знаю, так же ли "неуказанные" примеры в спецификации применяются к операторам возврата
Я просто говорю, что ваша первоначальная структура вопроса затрудняла понимание, нет «во время возврата», и вопрос об оценке по сравнению с назначением мог бы быть более ясным. Измените аргументы на присваивание, и вы увидите, что правая сторона оценивается первой go.dev/play/p/zuNvsiN0j1K. Если вы все еще считаете, что спецификация здесь неясна, список рассылки и система отслеживания проблем — лучшие места для обсуждения.
Извиняюсь за неясный вопрос, я не собирался спрашивать о порядке справа и слева, а о надежности порядка выполнения аргументов справа в многозначном возврате. В спецификации перечислены только примеры, в которых порядок не указан для среза и карты, и я не был уверен, что он также не указан для примитивов взамен.
Да! Это действительно отличный вопрос.
Другой способ написать этот код:
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
}
Кроме того, вы также можете сделать это, что может быть полезно, если вы оборачиваете транзакцию db (например) в обработчик ошибок.
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 будут оцениваться до того, как будут возвращены вызывающему объекту.
Да. Как вы обнаружили, язык определяет порядок оценки для некоторых вещей:
при оценке операндов выражения, оператора присваивания или возврата все вызовы функций, вызовы методов и операции связи оцениваются в лексическом порядке слева направо.
Естественно, все, что не является вызовом функции, вызовом метода или операцией связи, остается неопределенным в отношении порядка вычисления в выражении, операторе присваивания или возврата.
Да. Даже если компилятор не обновляется. Если вы ожидаете, что он будет иметь какой-либо конкретный результат, который зависит от порядка вычисления, тогда код уже сломан в том смысле, что язык не говорит, что код будет делать то, что вы думаете.
Такого различия нет.
10 <nil>
(как это происходит на игровой площадке) или он вообще может выводить 0 <nil>
?»Да, он может выводить 0, <nil>. По сути, это то же самое, что и в предыдущем примере run, где теперь замыкание f было преобразовано в функцию с именем inc.
Спасибо за прямые и лаконичные ответы на мои вопросы
Я имел в виду всегда, независимо от версии/поставщика компилятора. Я обновлю вопрос, чтобы быть более конкретным