Slide 1

Slide 1 text

range over funcの エラー処理 Go Conference 2024 Pre Party

Slide 2

Slide 2 text

自己紹介 ● MakKi (牧内 大輔) ○   ● KLab株式会社 ○ オンライン対戦の通信基盤を Goで実装 ○ github.com/KLab/wsnet2 ● 過去の発表 ○ Goとテストとインプロセス DB ○ 型パラメータが使えるようになったので LINQを実装してみた ○ ホットリロードツールの作り方 ○ … makiuchi-d @makki_d

Slide 3

Slide 3 text

range over funcとは ● Go 1.22 で GOEXPERIMENT入り ● for文のrangeに関数を渡せる ○ func(yield func() bool) ○ func(yield func(V) bool) ○ func(yield func(K, V) bool) ● yieldした回数だけループが回る ○ breakするとyieldがfalse ■ それ以降yieldするとpanic func Three(yield func(int) bool) { if !yield(1) { return } if !yield(2) { return } if !yield(3) { return } } func main() { for n := range Three { fmt.Println(n) } // Output: // 1 // 2 // 3 } https://go.dev/play/p/HguOV9bxf59

Slide 4

Slide 4 text

関数内でエラー発生 ● 呼び出し側(forループ)に errorの値をどうにかして伝えたい ● yieldを呼ぶか終了するかしかできない ○ yield: errorを渡せない ○ 終了: 正常終了と見分けがつかない  いまいちな方法〜いいかんじの方法を    4パターン考えてきました func Three(yield func(int) bool) { if !yield(1) { return } err := fmt.Errorf("Error!") } func main() { for n := range Three { fmt.Println(n) } } どうやって伝える?

Slide 5

Slide 5 text

1. panicする? ● forループ側にpanicが伝搬 ○ recoverすればエラーを取り出せる ● 利用者にrecoverさせるのは不親切 func Three(yield func(int) bool) { if !yield(1) { return } err := fmt.Errorf("Error!") panic(err) } func main() { var err error func() { defer func() { err, _ = recover().(error) }() for n := range Three { fmt.Println(n) } }() if err != nil { fmt.Println(err) } } https://go.dev/play/p/7h-IXsacSb1

Slide 6

Slide 6 text

2. 受け取り用ポインタ? ● error型変数を用意 ● errorポインタを受け取り、関数を返す ○ ポインタの先にerrorを設定して終了する関数 ● ループを抜けてからnullチェック ● 実装が複雑 func Three(ep *error) func(func(int) bool) { return func(yield func(int) bool) { if !yield(1) { return } err := fmt.Errorf("Error!") *ep = err } } func main() { var err error for n := range Three(&err) { fmt.Println(n) } if err != nil { fmt.Println(err) } } https://go.dev/play/p/B-fSKCH88AW

Slide 7

Slide 7 text

3. structに詰めてyield? ● 値とerrorをまとめたstructを定義 ● そのstructに詰めてyieldに渡す ● ループ内でstructの中のerrorをチェック ● structの定義が必要 ● 毎回詰めたり取り出したり面倒 type ValErr struct { Val int Err error } func Three(yield func(ValErr) bool) { if !yield(ValErr{1, nil}) { return } err := fmt.Errorf("Error!") yield(ValErr{0, err}) } func main() { for ev := range Three { if ev.Err != nil { fmt.Println(ev.Err) break } fmt.Println(ev.Val) } } https://go.dev/play/p/ff0FTnCnfj7

Slide 8

Slide 8 text

4. 2値のyield! ● yield func(K, V) bool ● K, Vともにどんな型でもOK ○ mapのようなcomparable制約はない ● 値とerrorをyieldに渡す ○ for文側で値とerrorを受け取れる ● Goとしてよく見る形 ● 実装もシンプル ○ ただしKey-Valueを返すときはKをstructに…… func Three(yield func(int, error) bool) { if !yield(1, nil) { return } err := fmt.Errorf("Error!") yield(0, err) } func main() { for n, err := range Three { if err != nil { fmt.Println(err) break } fmt.Println(n) } } https://go.dev/play/p/0VNRQSv7ELe

Slide 9

Slide 9 text

まとめ range for funcでエラーを伝えたいときは func(yield func(V, error) bool) の形で関数を実装すると綺麗!

Slide 10

Slide 10 text

宣伝 技術書典16 KLab Tech Book Vol.13 電子版0円(電子+紙 500円) 既刊もよろしく