A Tour of Go メモ ~Flow control statements: for, if, else, switch and defer~

## はじめに

Go の勉強のため、[A Tour of Go](https://go-tour-jp.appspot.com/flowcontrol/1)に取り組んでいる
今回は`Flow control statements: for, if, else, switch and defer`章について、学んだことを記していく

まとめ記事は[こちら](https://qiita.com/eyuta/items/77ac58e4d9141898c733)

## 使えそうなスニペット

※筆者は VSCode を使用

### `for`

初期値、条件式、後処理、スコープ内部の順にカーソルが移動する

```go
for i := 0; i < count; i++ {

}
```

### `if`

条件式、スコープ内部にカーソルが移動する
`el` -> `else { }` で else ブロックを追加できる

```go
if condition {

}
```

### `ei`

一発でも作れる

```go
if condition {

} else {

}
```

### `switch`

条件式、case、スコープ内部にカーソルが移動する
`cs` -> `case condition:` で条件を追加できる

```go
switch expression {
case condition:

}
```

### `df`

`defer func()`

### `pn`

`panic("")`

### `recover`

こちらは存在しなかったので、自作した
(`Preferences: Configure User Snippets`コマンドから作成可能)
出力されるのは、

```go
defer func() {
		if r := recover(); r != nil {

		}
}()
```

User Snippet の Json は、

```json
	"defer recover": {
		"prefix": "recover",
		"body": [
			"defer func() {",
			"    if r := recover(); r != nil {",
			"        $1",
			"    }",
			"}()",
		],
		"description": "defer recover"
	}
```

## ページごとの補足

### For continued

for 文で定義した変数を、条件式で使わなくても良い

```go
	x := 1
	for i := 0; 0 < x; {
		fmt.Println(i) // 流石にスコープ内で使わないと怒られる
	}
```

for で定義する変数は、定義済みでも良い

```go
	x := 1
	for x = 0; x < 10; x++ {
		fmt.Println(x)
  }
```

### If and else

- if ステートメントで宣言された変数は、`else if`ブロック内でも使える
- `else if`ステートメントで宣言された変数も、後続の`else if`、`else`ブロック内でも使える
- 同じ名前の変数を宣言した場合、上書きされる(=スコープがネストされる？)

```go
	x := 1 // 1,2,3,4
	if y := 1; x == 1 {
		fmt.Println("x == 1 : y = ", y)
	} else if y := 2; x == 2 {
		fmt.Println("x == 2 : y = ", y)
	} else if y := 3; x == 3 {
		fmt.Println("x == 3 : y = ", y)
	} else {
		fmt.Println("x  > 3 : y = ", y)
	}
	// x == 1 : y = 1
	// x == 2 : y = 2
	// x == 3 : y = 3
	// x  > 3 : y = 3
```

### Switch with no condition

Switch の case の条件式では変数宣言ができないため、例えば上述した`if-else`は、置き換えることができなさそう

### Defer

`A Tour of Go`では言葉少なに紹介されているため、[Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover)(日本語訳は [こちら](https://python.ms/defer-panic-and-recover/))の内容がメインになる

defer の使い方の 1 つは、他言語でいう`finally`
(Python でいう`with`、Kotlin でいう`use`の代わりにもなる)

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

他にも panic, recover があり、それぞれ以下のように対応しそう

- panic: throw error
- recover: catch

以下はエラーハンドリングについて、公式のサンプルプログラム

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

これを実行すると、以下のようになる

```log
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.
```

少し癖がありますが、使い慣れると便利そう
何度も`try-catch`を書かずに済みそうだ

## 雑感

このあたりは他言語と似通った仕様が多いので、難しい印象はない(最後の defer は別だが)
Go 言語は言語の仕様変更・追加が少ないので、数年前の記事が当てになるのが良い