LoginSignup
2
0

More than 3 years have passed since last update.

待って、その Defer 本当に実行されますか?

Last updated at Posted at 2020-02-08

社の勉強会で指摘されたことを自戒の意を込めてポストする。

Defer おさらい

defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。

上記は Tour of go より。HTTPクライアントの実装やファイルの読み書きをするときにリソースのクローズする際などによく使われる。

例えば以下のようなコード。

func ReachableDefer() (err error) {

    resp, err := http.Get("http://sample.com/")
    if err != nil {
        return err // 遅延実行されない
    }

    defer func() {
        if err := resp.Body.Close(); err != nil {
            log.Fatal("close resources", err)
        }
    }()

    _, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    return err
}

defer の宣言の手前に return の宣言がある場合、その return 実行時には defer の処理は実行されない。

故に、上記コードにおいては http.Get("http://sample.com/") の読み込みが失敗した場合、defer の遅延実行は走らない。

この時、respnil となるのでリソースをクローズしなくても問題にはならない。

Defer を宣言する位置に注意

さきほどのコードにおいて defer の宣言をうっかり下記のようにすると、読み込んだリソースが閉じられることなく処理が終了してしまい、読み込んだリソースがリークしてしまう。

func UnreachableDefer() (err error) {

    resp, err := http.Get("http://sample.com/")
    if err != nil {
        return err // 遅延実行されない, resp は空なのでOK.
    }

    _, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        return err // 遅延実行されない, resp はクローズされずにリークするのでNG.
    }

    defer func() {
        if err := resp.Body.Close(); err != nil {
            log.Fatal("close resources", err)
        }
    }()

    return err
}

ioutil.ReadAll(resp.Body) の処理が失敗した場合は遅延実行は走らない。

最後に

いつ如何なるときも関数の終了時に遅延実行してくれるものと勘違いしてました。
学び。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0