Go で "broken pipe" を無視したい僕達がハマる罠

# ぶろーくんぱいぷ？

　たとえば、Goでこんなプログラムを書いたとして、

```go
// main.go
package main

import "fmt"

func main() {
	for {
		fmt.Println("Wow!")
	}
}
```

　これを `head` コマンドにパイプすると、

```
$ go run main.go |head
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
signal: broken pipe
```

　最後になぜか怒られるというアレですね。

　この場合、main.go は無限に「Wow!」を書こうとしているわけですが、`head` は最初の10行を表示すると「俺ができるのはここまでだぜ」と言ってお亡くなりになってしまい、結果的に main.go の行き場のなくなったヤル気が「signal: broken pipe」という怒りとなって現れるという寸法です。

　このままでも特に実害はないのですが、やはりエラーメッセージが表示されるというのはいかにもカッコがつきませんよね。

　というわけで、"broken pipe" を無視してきれいに終われるようにしましょう。

# `EPIPE` を捕捉する

　やり方は簡単です。`fmt.Println` の戻り値をちゃんと確認しましょう:

```go
n, err := fmt.Println("Wow!")
```

　この `err` が "broken pipe" を示す値だったら、エラーを無視します。さて、どうやって "broken pipe" と判別すれば良いのでしょうか？

　[golang.org](https://golang.org/pkg/) とかで検索すると、`syscall.EPIPE` という値がありますね。なるほど、[LinuxのMan page](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/pipe.7.html)を見ても、`EPIPE` というのは閉じられているパイプに書き込んでしまった際に返されるエラー値のようです。

　では、これを使って、

```go
// main.go
package main

import (
	"fmt"
	"syscall"
)

func main() {
	for {
		_, err := fmt.Println("Wow!")
		if err != nil {
			if err == syscall.EPIPE {
				break
			} else {
				panic(err)
			}
		}
	}
}
```

　こんなふうにすれば良さそうですね！

![chotto-matte.png](https://qiita-image-store.s3.amazonaws.com/0/100577/1efac791-29aa-8573-fcb2-0e15daeb5bb2.png)　**それは罠よ！！**

　えっ！？

```
$ go run main.go |head
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
panic: write /dev/stdout: broken pipe

goroutine 16 [running]:
runtime.panic(0x4aec20, 0xc208022180)
	/usr/lib/go/src/pkg/runtime/panic.c:279 +0xf5
main.main()
	/gocode/pipe/main.go:15 +0x170

goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
	/usr/lib/go/src/pkg/runtime/mheap.c:507
runtime.goexit()
	/usr/lib/go/src/pkg/runtime/proc.c:1445

goroutine 18 [runnable]:
bgsweep()
	/usr/lib/go/src/pkg/runtime/mgc0.c:1976
runtime.goexit()
	/usr/lib/go/src/pkg/runtime/proc.c:1445

goroutine 19 [runnable]:
runfinq()
	/usr/lib/go/src/pkg/runtime/mgc0.c:2606
runtime.goexit()
	/usr/lib/go/src/pkg/runtime/proc.c:1445
exit status 2
```

　なんじゃこりゃーー


# DISARM THE TRAP

　どうして `EPIPE` で "broken pipe" が捕捉できなかったのでしょう？
　原因はそれぞれの型を表示してみればわかります:

```go
// main.go
package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	for {
		_, err := fmt.Println("Wow!")
		if err != nil {
			fmt.Fprintf(os.Stderr, "err  : %T\n", err)
			fmt.Fprintf(os.Stderr, "EPIPE: %T\n", syscall.EPIPE)
			break
		}
	}
}
```

　実行結果:

```
$ go run main.go |head -n 1
Wow!
err  : *os.PathError
EPIPE: syscall.Errno
```

　見事に型が違いますね。

~~　**あれ？でも、Go言語では型が違うものどうしを比較したらコンパイル時に型エラーになるんじゃなかったでしたっけ？**~~
**※追記：これがコンパイルが通ってしまう理由ではなかったようです。詳細は↓のコメント欄を参照**

　これがこの罠の罪深いところ。[`EPIPE` は untyped constant として定義されている](https://golang.org/pkg/syscall/#EPIPE) ため、それ自体では **型付けされていない** のです。

　言ってる意味がわからないという方には、あとで [Robさんのブログポスト](https://blog.golang.org/constants) を読んでもらうこととして、とりあえずここでは `EPIPE` はソースコード中で書かれた場所によって "いい感じに" 型を合わせてくれるのだと思っておいてください。

　ここでは比較の対象が `error` 型の変数ですから、`EPIPE` が `error` 型として振る舞ってくれてくれている結果、コンパイルエラーにならずに通ってしまうんですね。


# ちゃんと `EPIPE` を捕捉する

　結局、どうするべきだったのでしょう？

　`Println` が返すエラーの型である `*os.PathError` は、その原因となったエラーの値を持っていますので、それが `EPIPE` なのかを確認すれば OK です:

```go
// main.go
package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	for {
		_, err := fmt.Println("Wow!")
		if err != nil {
			if e, ok := err.(*os.PathError); ok && e.Err == syscall.EPIPE {
				break
			} else {
				panic(err)
			}
		}
	}
}
```

　型アサーションを書くのがちょっと面倒ですが、仕方がないですね。

　実行すると:

```
go run main.go |head
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
Wow!
```

　おめでとうございます！見事に "broken pipe" が消えましたね！

![disarmed.png](https://qiita-image-store.s3.amazonaws.com/0/100577/6e7aba85-985d-7758-9a8b-66f78ecc5d79.png)