Отложить до выхода за пределы функции

#go #deferred

#Вперед #отложено

Вопрос:

Общий шаблон, который я использую, это:

 resource.open()
defer resource.close()
  

иногда проверка ошибок между ними, что приводит к:

 err := resource.open()
if err != nil{
     //do error stuff and return
}
defer resource.close()
  

Иногда мне потребуется несколько открытых / закрытых ресурсов подряд, что приведет к повторению предыдущих 5 строк одна за другой. Это изменение может повторяться дословно несколько раз в моем коде (где мне нужны все те же ресурсы).

Было бы замечательно обернуть все это в функцию. Однако это приведет к закрытию ресурса, как только вызов функции завершится. Есть ли какой-либо способ обойти это — либо отложить «повышение уровня» стека вызовов, либо каким-либо другим способом?

Комментарии:

1. Вы не можете вызвать другую функцию, чтобы отложить что-либо. У вас может быть функция, которая инициализирует ресурсы и откладывает их закрытие, а затем вызывает другую функцию, которую вы передали в качестве аргумента с этими ресурсами.

2. defer выполняется, когда функция, в которой она вызывается, возвращает, точка. Повторение 5 тривиальных строк невероятно распространено в Go.

3. @Adrian Верно. Жалобы на это — другая тема 🙂 Моя проблема заключается в том, что я повторяю одни и те же 20 тривиальных строк, где, скажем, мне нужны одни и те же четыре ресурса в нескольких разных местах.

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

5. Нет, открытие / закрытие ресурсов — это нормально. Необходимость открывать / закрывать одну и ту же кучу ресурсов во многих местах вашего кода — это проблема дизайна.

Ответ №1:

Один из способов сделать это — использовать функцию «инициализатора» с обратным вызовом:

 func WithResources(f func(Resource1, Resource2)) {
   r1:=NewResource1()
   defer r1.Close()
   r2:=NewResource2()
   defer r2.Close()
   f(r1,r2)
}

func F() {
  WithResources(func(r1 Resource1, r2 Resource2) {
    // Use r1, r2
  })
}
  

Сигнатура функции f зависит от вашего конкретного варианта использования.

Другой способ — использовать структуру для набора ресурсов:

 type Resources struct {
   R1 Resource1
   R2 Resource2
   ...
}

func NewResources() *Resources {
   r:=amp;Resources{}
   r.R1=NewR1()
   r.R2=NewR2()
   return r
}

func (r *Resources) Close() {
   r.R1.Close()
   r.R2.Close()
}

func f() {
   r:=NewResources()
   defer r.Close()
   ...
}
  

Комментарии:

1. имеет смысл, но кажется очень специфичным для подписи обратного вызова.

2. Отредактировано, чтобы включить другой метод.

Ответ №2:

Было бы замечательно обернуть все это в функцию.

Скорее всего, многим людям не понравилось бы читать такой код. Так что «замечательно» может быть очень субъективным.

Однако это приведет к закрытию ресурса, как только вызов функции завершится.

Точно.

Есть ли какой-нибудь способ обойти это […]?

Нет.

Комментарии:

1. итак, вы утверждаете, что проблемы нет, и я все выдумываю?

2. @WreckFish я ничего не утверждаю. Я констатирую истинный факт, что вы не можете изменить способ работы defer. (Это был ваш вопрос.)

3. Смотрите принятый ответ.