Go - непоследовательная оценка отложенных функций

Я экспериментирую с Go и наблюдаю неожиданное поведение с отложенными функциями. Рассмотрим следующую программу, которая увеличивает глобальную переменную на заданную величину.

package main

import "fmt"

var z = 1

func main() {

    defer increaseZ(10)
    defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
    defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")

    fmt.Println("z =", z, "Main Value")
}

func increaseZ(y int) int {
    z += y
    println("z =", z, "Inside Increase Function")
    return z
}

Когда запускается на игровой площадке Go, это выводит:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 61 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1

Если я переключу порядок отложенных функций, это даст другой эффект:

defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")
defer increaseZ(10)

Выходы:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 61 Inside Increase Function
z = 51 Deferred Value 2
z = 21 Deferred Value 1

В документации Go указано:

Аргументы отложенного вызова оцениваются немедленно, но вызов функции не выполняется до возврата из окружающей функции.

Таким образом, оцениваемые аргументы могут объяснить, почему возвращаемое значение Main Value равно 51, а не 61, поскольку операторы fmt.Println принимают increaseZ в качестве аргумента, но defer increaseZ(10) не будет вызываться до тех пор, пока основная функция не вернется. .

Однако это не объясняет, почему в первом примере increaseZ(10) выводится до завершения main, а во втором примере - после завершения main.

Я был бы признателен, если бы кто-нибудь мог помочь мне понять, что здесь происходит, поскольку это выглядит как благодатная почва для трудных для диагностики ошибок в дальнейшем.


person PassKit    schedule 28.03.2015    source источник
comment
Это интересно.   -  person fuz    schedule 28.03.2015
comment
Игровая площадка не чередует stdout и stderr. См. play.golang.org/p/KhR-DqqNZ3. Измените println на fmt.Println, чтобы получить ожидаемый результат.   -  person Cerise Limón    schedule 28.03.2015
comment
Не используйте println. Из документации: […] в зависимости от реализации […] не гарантируется, что язык останется на языке.   -  person Dave C    schedule 28.03.2015


Ответы (3)


Вы не согласны с целевым назначением печати.

stdout: fmt.Println

stderr: println

Пишите в то же место назначения печати.

package main

import "fmt"

var z = 1

func main() {

    defer increaseZ(10)
    defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
    defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")

    fmt.Println("z =", z, "Main Value")
}

func increaseZ(y int) int {
    z += y
    fmt.Println("z =", z, "Inside Increase Function")
    return z
}

Выход:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1
z = 61 Inside Increase Function

or,

package main

import (
    "fmt"
    "os"
)

var z = 1

func main() {

    defer increaseZ(10)
    defer fmt.Fprintln(os.Stderr, "z =", increaseZ(20), "Deferred Value 1")
    defer fmt.Fprintln(os.Stderr, "z =", increaseZ(30), "Deferred Value 2")

    fmt.Fprintln(os.Stderr, "z =", z, "Main Value")
}

func increaseZ(y int) int {
    z += y
    println("z =", z, "Inside Increase Function")
    return z
}

Выход:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1
z = 61 Inside Increase Function
person peterSO    schedule 28.03.2015
comment
println запись в stderr прекрасно все объясняет. Не могли бы вы рассказать, где это задокументировано? - person PassKit; 28.03.2015
comment
@PassKit: в встроенном псевдопакете Go. func println: встроенная функция println форматирует свои аргументы в зависимости от реализации и записывает результат к стандартной ошибке. - person peterSO; 28.03.2015

Дело не в отложенных оценках. Речь идет о печати. println функция задокументирована для полноты, но не гарантируется, что она вообще останется на языке. Также Stdout и Stderr по замыслу слились в один поток на Playground. Если вы везде используете fmt.Println(...), http://play.golang.org/p/PU3hxHCazA или явно определите fmt.Fprintln(os.Stdout, ... http://play.golang.org/p/OQpOQR2vm0 все будет работать, как ожидалось.

person Uvelichitel    schedule 28.03.2015

Я подозреваю, что это ошибка игровой площадки Go. Когда я компилирую и запускаю эту программу на своей машине, она дает ожидаемый результат. Был подан отчет об ошибке, относящийся к этой проблеме.

person fuz    schedule 28.03.2015
comment
После проверки он работает должным образом при компиляции на go1.4.2 darwin/amd64 - person PassKit; 28.03.2015
comment
ПРИМЕЧАНИЕ. Отчет об ошибке Go был отклонен как недействительный. Бага Go нет. Поведение такое, как ожидалось. Ошибка в программе. Подробности смотрите в моем ответе. - person peterSO; 28.03.2015
comment
Он не был отклонен, я его закрыл. Что странно, мне не удалось воспроизвести это поведение другими способами. - person fuz; 28.03.2015