Golang 〜Defer, Panic, and Recover〜

## Defer
関数呼び出しをリストに Push する。周囲の関数を実行後、リストに貯まった関数が実行される。
Defer は、様々な Clean up 処理に使用される。

### サンプルコード
ファイルをコピーする下記のコードを例にする。

```golang
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(dstName)` が失敗すると、コピー元のファイルを close することができない。
このバグは、以下のように `src` に対して明示的に `Close()` を呼び出すことで解決できる。

```golang
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` によって、各ファイルをオープン後に close することが保証される。

### 3つのルール
 １. `Defer` 関数の引数は、Defer ステートメントが評価される時に評価される

```go:main.go
func a() {
	i := 0
	defer fmt.Println(i)
	i++
	return
}

func main() {
	b()
}
```

```
$ go run main.go 
0
```
上記を実行すると、0が出力される。`i`は、`Println()`が call された際に評価されることがわかる。

 ２. `Defer` 関数の呼び出しは、 LIFO(Last In First Out) にて実行される

```golang:main.go
func b() {
	for i := 0; i < 4; i++ {
		defer fmt.Println(i)
	}
}
func main() {
	b()
}
```

```
$ go run main.go 
3
2
1
0
```

３. `Defer` 関数は、関数の戻り値を読み取り、新しい値を割り当てることができる

```golang:main.go
func c() (i int) {
	defer func() { i++ }()
	return 1
}

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

```
$ go run main.go 
2
```

上記の例では、`c()`の返り値 `i` に1を足す `Defer` 関数を作成している。よって、2が返ってくる。
3のルールは、エラー時の挙動を決定するにあたり、非常に有用である。

## Panic
Panic() は、通常の制御の流れを止めて Panic を開始する組み込み関数である。関数FがPanicを呼び出すと、Fの実行が停止し、F内のすべての `Defer` 関数が正常に実行されてから、Fは呼び出し元に戻る。 Caller にとって、Fはパニックへの呼び出しのように動作する。プロセスは、現在の goroutine (Goのランタイムによって管理される軽量スレッド)内のすべての関数が戻るまで Stack を上っていく。戻ると、プログラムがクラッシュする。
Panic は、 Panic を直接呼び出すことによって開始できる。また、範囲外の配列アクセスなどのランタイムエラーが原因で発生することもある。

## Recover
Recover は、 Panic 状態の goroutine の制御を取り戻す組み込み関数である。 Recover は、 `Defer` 関数内でのみ使用可能。通常の実行中、 Recover の呼び出しは nil を返し、他の効果はない。現在の goroutine が Panic になっている場合、Recoverの呼び出しは Panic に与えられた値をキャプチャし、通常の実行を再開する。

## サンプルコード

```golang:main.go
package main

import "fmt"

func main() {
	f()
	fmt.Println("Returned normally from f.")
}

func f() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	fmt.Println("calling g.")
	g(0)
	fmt.Println("Returned nomally from g.")
}

func g(i int) {
	if i > 3 {
		fmt.Println("panicking!!!")
		panic(fmt.Sprintf("%v", i))
	}
	defer fmt.Println("Defer in g", i)
	fmt.Println("Printing in g", i)
	g(i + 1)
}
```

```
$ go run main.go 
calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
panicking!!!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
```

`Recover()` がなかった場合、以下のように goroutine のスタック最上部まで戻り、プログラムを終了させる。

```golang:main.go
package main

import "fmt"

func main() {
	f()
	fmt.Println("Returned normally from f.")
}

func f() {
	// defer func() {
	// 	if r := recover(); r != nil {
	// 		fmt.Println("Recovered in f", r)
	// 	}
	// }()
	fmt.Println("calling g.")
	g(0)
	fmt.Println("Returned nomally from g.")
}

func g(i int) {
	if i > 3 {
		fmt.Println("panicking!!!")
		panic(fmt.Sprintf("%v", i))
	}
	defer fmt.Println("Defer in g", i)
	fmt.Println("Printing in g", i)
	g(i + 1)
}
```

```
$ go run main.go 
calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
panicking!!!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4

（StackTrace は省略。。。）
```

Panic と Recover の実例として、Go標準ライブラリのjsonパッケージを参照すると良い。jsonパッケージは、一連の再帰関数を使用してインターフェースをエンコードする。値のトラバース中にエラーが発生した場合、 `Panic()` が呼び出されてスタックが最上位の関数呼び出しに巻き戻され、 Panic から回復して適切なエラー値が返される([encode.go](https://golang.org/src/encoding/json/encode.go))。

Goライブラリの規則では、パッケージが内部で Panic を使用している場合でも、その外部 API は明示的なエラー戻り値を表示する、としている。

他に、 `Defer` の使用例として、 Mutex のリリースや、フッターの出力などがある。

```golang
mu.Lock()
defer mu.Unlock()
```

```golang
printHeader()
defer printFooter()
```


## 参考
[Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover)