Go 言語の遅延処理(defer)

Go 言語の遅延処理の defer についてまとめてみた。

基本的な使い方
・funcA の中で funcB の前に defer をつけると funcB の実行を funcA が終了するタイミングまで遅延することができる。

func funcA() {
	defer funcB()
	fmt.Println("funcA 実行中")
}

func funcB() {
	fmt.Println("funcB 実行中")
}

func main() {
	funcA()
}

出力

funcA 実行中
funcB 実行中

主にオープンとクローズ、接続と切断、ロックとアンロックなど一対となる操作で行われる。
defer は panic したときにも呼ばれるためこうすることでファイルのクローズ処理、通信の切断処理を確実に行えるようになる。(ただし os.Exit が実行した場合は defer は実行されない。)

複数の関数に defer をつけることもでき、その場合には呼び出し順序は遅延された順序の逆順になる。スタックのイメージ(LIFO)。

for ループ内で defer を実行する場合には注意が必要。
どういうことかというと。以下のようにすると funcA を出るまで f.Close() が実行されず、すべてのファイルの処理が終わるまでどのファイルも閉じられないためファイル記述子が枯渇する可能性がある。

func funcA() {
	filenames := []string{"sampleA.txt", "sampleB.txt", "sampleC.txt"}
	for _, filename := range filenames {
		f, err := os.Open(filename)
		if err != nil {
			fmt.Println(err)
		}
		defer f.Close()
	}
}

そうならないために遅延をするスコープを funcA ではなく、別途関数 funcB を作成する。このようにするとファイルのクローズ処理は funcB が終了するタイミングで実行されるため 1 つづのファイルごとにオープン、クローズが行われる。

func funcA() {
	filenames := []string{"sampleA.txt", "sampleB.txt", "sampleC.txt"}
	for _, filename := range filenames {
		funcB(filename)
	}
}

func funcB(filename string) {
	f, err := os.Open(filename)
	if err != nil {
		fmt.Println(err)
	}
	defer f.Close()
}

defer の他の使い方としては返り値の書き換えというものがある。
Go 言語には名前付き結果パラメータというものがあり、関数の返り値に名前をつけることができ defer を用いることによって返り値を書き換える事ができる。以下のように使用する。(ここで変数 result が名前付き結果パラメータ)

func funcA() (result int) {
	defer func() {
		result++
	}()
	return 1
}

func main() {
	result := funcA()
	fmt.Printf("result: %v", result)
}

出力結果は 1 ではなく、2 になる。

result: 2

1 が return された後の result に対して defer を用いて返り値の書き換えを行っている。

いいなと思ったら応援しよう!