Отмена defer вызова функции в Golang

в 18:15, , рубрики: defer, golang, golang defer, методы, отложенные функции, паттерны, функции

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

Представьте, что у вас есть 10 кейсов, в одном из которых не вам не нужен вызов defer func. Что же тогда делать......

Оператор defer помещает вызов функции в список. Список сохраненных вызовов выполняется после того, как возвращается функция. Defer обычно используется для упрощения функций, выполняющих различные действия по очистке.

package main

import "fmt"

func foo(){

    defer fmt.Println("Deffered out")

    fmt.Println("End of func")

}

func main(){
    foo()
}

Результат:

End of func
Deffered out

После выполнения функции и вывода "End of func" функция завершается и вызывается defer func. По итогу, имеем такой вывод.

Рассмотрим примеры из официальной документации (https://go.dev/blog/defer-panic-and-recover).

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

Это работает, но есть ошибка. Если вызов os.Create не удался, функция вернется, не закрыв исходный файл. Это можно легко исправить, поместив вызов src.Close перед вторым оператором возврата, но если бы функция была более сложной, проблему было бы не так легко заметить и решить. Используя defer, мы можем гарантировать, что файлы всегда будут закрыты.

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

defer гарантирует, что, независимо от количества возврата в функции, файлы будут закрыты.

Работа defer осуществляется по трём правилам:

  1. Аргументы defer функции передаются на этапе создания defer call.

  2. defer функции вызываются в порядке Last In First Out после завершения внешней функции.

  3. defer функция умеет работать с возвращаемым значением по умолчанию функции.

Рассмотрим примеры кода для каждого пункта. Передача аргументов в defer функцию:

package main

import "fmt"

func foo() { 
	a := 10
	defer fmt.Println(a)
	a += 20
	fmt.Println("End of func")
}

func main(){
	foo()
}

По завершении работы функции foo() наблюдается вот такой вывод:

End of func
10

Вызов defer функций в порядке LIFO:

package main

import "fmt"

func foo() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

func main(){
	foo()
}

Результат:

3210

Последняя вызванная defer функция выполняется первой (LIFO)

defer функция умеет работать с возвращаемым значением по умолчанию функции:

package main

import "fmt"

func c() (i int) {
	defer func() { i += 100 }()
	return 100
}

func main() {
	fmt.Println(c())
}

Функция возвращает i = 100, потом defer функция увеличивает i на 100, отсюда вывод:

200

defer с возможностью отмены его вызова

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

package main

import "fmt"

func foo(a int){
	var Execute *bool = new(bool)
	*Execute = true

	defer fmt.Println("End of func")
	
	defer func(ex *bool) {
		if *ex {
			fmt.Println(a / 2)
		}
	}(Execute)

	switch{
	case a % 2 == 0:
		fmt.Printf("%d is evenn", a)
		return
	case a % 2 == 1:
		fmt.Printf("%d is oddn", a) 
		*Execute = false
		return
	default:
		*Execute = false
		return
	}
}

func main(){
	foo(4)
	fmt.Println()
	foo(5)
}

Так как аргументы передаются на момент создания defer функция, будем передавать указатель на bool, чтобы проверять нужно ли нам вызывать функция в defer. На вход поступает 4, оно чётное, поэтому после return вызывается defer функция, в которой проверяется Execute, поэтому выводится a / 2.

На вход поступает цифра 5, оно нечётная, поэтому в case, разыменовывая указатель, изменяется значение Execute. Следовательно, после return в defer функции мы не выводим нашу цифру, поделённую пополам, так как *Execute = false.

4 is even
2
End of func

5 is odd
End of func

Автор: Kpatoc452

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js