LoginSignup
0
2

More than 3 years have passed since last update.

Goのdeferの仕組みをきちんと理解する

Posted at

deferって関数の最後で実行させたい処理に使うんでしょ、程度に考えていたが、きちんと理解をしようとdeferについて深堀りしてみた。

deferについてざっくり説明

  • deferで処理を記述しておくとfuncの最後で実行してくれる。
  • 以下の例だと、"今日は"が実行され、funcの終わりになるので、その後"金曜日です。"が実行される。
func hogehoge() {
    defer fmt.Println("金曜日です。")
    fmt.Println("今日は....")
}
今日は....
金曜日です。

ちょっと深堀り

Tour of Goの説明によると

  1. 囲まれているfunctionがreturnされるまで実行が見送られる。
  2. defer呼び出しの引数は即時に評価されるが、実行がreturnされるまで見送られる。

との説明がある。
1はなんとなく理解できるが、2の"即時に評価される"という記述が気になったのであとで詳しく解説したい。

また、プログラミング言語Goの説明によると

  1. deferを使うことでリソースの開放などがきれいに記述できる。
  2. deferの正しい位置は資源の獲得に成功した直後

という旨の説明がある。
以下が例である。

resp, err := http.Get(url)
defer resp.Body.Close() //本当はdeferの前にエラー判定いれるべき

Javaとかだと関数の最後にclose()を記述したりすることが多く、たまーにclose()を書き忘れたりすることもあるが、
goであればdefer使ってリソースを取得した直後に開放処理入れておくのが推奨されている。

即時に評価される、とは?

実際にコードを見たほうが早いと思うので、以下のサンプルを見て欲しい。

func main() {
    defer today()()
    fmt.Println("今日は....")
}

func today() func() {
    fmt.Println("金曜日ですか?")
    return func() {fmt.Println("それとも土曜日ですか?")}
}

これを実行するとどうなるだろうか?
deferはmainが終わる時に呼び出されるから、今日は...がまず呼び出され、次にtoday()内の金曜日ですか?、最後にそれとも土曜日ですか?という流れるになるかと思うかもしれない。

しかし、実行してみると

金曜日ですか?
今日は....
それとも土曜日ですか?

となるのである。さてなぜだろう?
ポイントはtoday()の返り値が関数型である点だ。

前項のdefer呼び出しの引数は即時に評価されるが、という部分に注目して欲しい。

defer today()()の記述の部分で評価が実施されるのである。(なんで括弧が2つあるのか?と一瞬思う方もいるかもしれないが、話がそれるので最後に解説したい)

なので、

fmt.Println("金曜日ですか?")

が実行される。その後、main内の処理に進み、

fmt.Println("今日は....")

が実行される。

その後、遅延実行で

fmt.Println("それとも土曜日ですか?")

という流れである。

defer today()()の括弧が2つあるのはなぜ??

括弧2ついらないんじゃない??と思うひともいるかもしれないが、これは必要である。ためしに括弧を1つで実施するとどうなるだろうか?

func main() {
    defer today()
    fmt.Println("今日は....")
}

func today() func() {
    fmt.Println("金曜日ですか?")
    return func() {fmt.Println("それとも土曜日ですか?")}
}
今日は....
金曜日ですか?

順番が変わった上に、土曜日が消えている。。。。

defer today() //1
defer today()() //2

この2つの記述は根本的に違うのである。

1はあくまで、deferの処理でtoday関数を実行する。つまり、main関数の最後にtoday()が実行されるのである。この処理では無名関数がreturnされているが、無名関数は実行されないということなのである。

それに対し、2はtoday()でリターンされた関数をmain関数の最後に実施するのである。わかりやすく記述すると2はdeferの処理で無名関数func() {fmt.Println("それとも土曜日ですか?")}を実行しているのである。

0
2
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
0
2