mooriii's blog

article icon

Goのdeferを理解したい

deferとは

Goの deferとは、渡した関数の実行を、呼び出し元の関数が終わるまで遅らせるものです。

defer.go
func main() {
	defer fmt.Println("2番目に実行")
	fmt.Println("最初に実行")
}
最初に実行
2番目に実行

defer では実行は遅延されますが、即時評価されます。

defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

どういうことかと言うと、 以下の例のように、 defer で評価された後に変数の値を変化させても実行時には反映されません。

defer.go
func main() {
	message := "2番目に実行"
	defer fmt.Println(message)
	message = "評価が終わってるのでprintされない"
	fmt.Println("最初に実行")
}
 
最初に実行
2番目に実行

deferの複数呼び出し

defer を関数内で複数呼び出した場合はLIFO(last-in-first-out)の順で実行されます。(いわゆるスタック)

defer.go
func main() {
	fmt.Println("hello")
	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}
	fmt.Println("world")
}
hello
world
9
8
7
6
5
4
3
2
1
0

どういうときに使うのか

よくあるのはクリーンアップ処理です。 公式ではファイルシステムの処理を例に出しています。

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.Openos.Createの処理が失敗した場合にソースファイルを閉じずに処理を終了してしまいます。

一方deferを使えば、return後に必ず実行してくれるので早期リターンをする場合に考慮することが減ります。

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)
}

個人的にはテストでDBのセットアップコードを書くときに、よくお世話になっています。

おわりに

せっかく個人ブログ作ったのにアウトプットの質とレベルを気にしすぎて勝手に投稿ハードルを上げてしまっていたので、思い切ってシンプルな内容にしてみました。

社会人として何年もエンジニアやってるはずなのに全然身についていないので、簡単な内容からどんどんアウトプットしていこうと思います。

もうちょいzennのスクラップくらい事実の羅列にしたほうが書きやすいかもな〜。(でも書きたくなっちゃうんですよね感情を。)

ではまた。

LGTM

この記事をシェアするx icon
アイコン画像
Takuto Mori@_mooriii

Wevoxというサービスのフロントエンジニアをしています。趣味は猫を眺めることです🐱