Очищение Голанга

Что произошло, когда debfer вызывается дважды, когда структура этого метода была изменена?

Например:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
for rows.Next() { 
  // do something
}
rows = Query(`SELECT FROM another`) 
defer rows.Close()
for rows.Next() {
  // do something else
}

который rows, когда последний rows.Close() называется?

Ответ 1

Это зависит от приемника метода и от типа переменной.

Короткий ответ: если вы используете пакет database/sql, ваш отложенный Rows.Close() будут правильно закрывать оба ваших экземпляра Rows, потому что Rows.Close() имеет приемник указателей и, потому что DB.Query() возвращает указатель (и поэтому Rows является указателем). См. Объяснения и пояснения ниже.

Чтобы избежать путаницы, я рекомендую использовать разные переменные, и будет ясно, что вы хотите и что будет закрыто:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
// ...
rows2 := Query(`SELECT FROM whatever`)
defer rows2.Close()

Я хотел бы указать на важный факт, который исходит из отложенной функции и ее параметров, которые оцениваются непосредственно, что указано в Эффективный Go в блоге и в Language Spec: Отложенные утверждения тоже:

Каждый раз, когда выполняется оператор "defer", значение функции и параметры для вызова оцениваются как обычно, а сохраняется снова, но фактическая функция не вызывается. Вместо этого отложенные функции вызывают непосредственно перед возвратом функции окружающего объекта, в обратном порядке они откладываются.

Если переменная не является указателем: вы будете наблюдать разные результаты при вызове метода, отложенного, в зависимости от способа приема указателя-указателя.
Если переменная является указателем, вы всегда увидите "желаемый" результат.

См. этот пример:

type X struct {
    S string
}

func (x X) Close() {
    fmt.Println("Value-Closing", x.S)
}

func (x *X) CloseP() {
    fmt.Println("Pointer-Closing", x.S)
}

func main() {
    x := X{"Value-X First"}
    defer x.Close()
    x = X{"Value-X Second"}
    defer x.Close()

    x2 := X{"Value-X2 First"}
    defer x2.CloseP()
    x2 = X{"Value-X2 Second"}
    defer x2.CloseP()

    xp := &X{"Pointer-X First"}
    defer xp.Close()
    xp = &X{"Pointer-X Second"}
    defer xp.Close()

    xp2 := &X{"Pointer-X2 First"}
    defer xp2.CloseP()
    xp2 = &X{"Pointer-X2 Second"}
    defer xp2.CloseP()
}

Вывод:

Pointer-Closing Pointer-X2 Second
Pointer-Closing Pointer-X2 First
Value-Closing Pointer-X Second
Value-Closing Pointer-X First
Pointer-Closing Value-X2 Second
Pointer-Closing Value-X2 Second
Value-Closing Value-X Second
Value-Closing Value-X First

Попробуйте на Go Playground.

Используя переменную указателя, результат всегда хорош (как и ожидалось).

Используя переменную non-pointer и используя приемник указателей, мы видим те же самые печатные результаты (последние), но если у нас есть приемник значений, он печатает 2 разных результата.

Объяснение для переменной без указателя:

Как указано, отложенная функция, включая приемник, оценивается при выполнении defer. В случае приемника указателя он будет адресом локальной переменной. Поэтому, когда вы назначаете ему новое значение и вызываете другой defer, получатель указателя будет снова тем же адресом локальной переменной (только указанное значение отличается). Таким образом, позже, когда функция будет выполнена, оба будут использовать один и тот же адрес дважды, но указанное значение будет таким же, как и указанное позже.

В случае приемника значения приемник представляет собой копию, которая выполняется при выполнении defer, поэтому, если вы назначаете новое значение переменной и вызываете другую defer, другая копия будет отличаться от предыдущего.

Ответ 2

Эффективный переход:

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

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

В вашем случае отсрочка будет ссылаться на экземпляр второй строки.
Две отложенные функции выполняются в порядке LIFO (как упоминалось также в Defer, Panic и Recover").

Как icza упоминается в его ответ и в комментариях:

2 отложенных метода Close() будут ссылаться на 2 различных значения строк, и оба будут правильно закрыты, потому что rows является указателем , а не типом значения.

Ответ 3

А я вижу, что rows всегда относится к последнему, http://play.golang.org/p/_xzxHnbFSz

package main

import "fmt"

type X struct {
   A string
}

func (x *X) Close() {
  fmt.Println(x.A)
}

func main() {
  rows := X{`1`}
  defer rows.Close()
  rows = X{`2`}
  defer rows.Close()
}

Вывод:

2
2

Поэтому, возможно, лучший способ сохранить объект - передать его функции: http://play.golang.org/p/TIMCliUn60

package main

import "fmt"

type X struct {
    A string
}

func (x *X) Close() {
    fmt.Println(x.A)
}

func main() {
    rows := X{`1`}
    defer func(r X) { r.Close() }(rows)
    rows = X{`2`}
    defer func(r X) { r.Close() }(rows)
}

Вывод:

2
1