struct内のzero valueの対処法

## フローチャート

![a.png](https://qiita-image-store.s3.amazonaws.com/0/7118/5b15c1b5-8f3b-1963-80d0-1a80d52c1a8f.png)

## 背景

goはnull安全な言語ではない。どころか、レシーバがnilの時の処理すら書ける[^1]。

```go:nilレシーバの活用例
package main

import "fmt"

type Name struct {
	First string
	Last  string
}

func (n *Name) String() string {
	if n == nil {
		return "N/A"
	}
	return fmt.Sprintf("%s, %s", n.Last, n.First)
}

func main() {
	name := &Name{"Taro", "Yamada"}
	fmt.Println(name.String()) // => Yamada, Taro
	name = nil
	fmt.Println(name.String()) // => N/A
}
```

一方で、interfaceのzero valueがnilであり、それに対してメソッド呼び出ししてしまうと、残念ながらヌルポになってしまう。

```go:ヌルポの例
package main

import (
	"bufio"
	"io"
	"os"
)

type Command struct {
	Input  io.Reader
	Output io.Writer
}

func (c *Command) Run() {
	scanner := bufio.NewScanner(c.Input)
	scanner.Scan()
	c.Output.Write(scanner.Bytes())
}

func main() {
	c := &Command{} // Oops! 初期化し忘れた。
	c.Run() // => SEGV
}
```

さて、この問題にどう対処するべきか。

## 適切にエラー処理する

当然のことであるが、nilが入る以上、そのケースを考慮した処理にするべきである。

```go:上記「ヌルポの例」のエラー処理版
package main

import (
	"bufio"
	"errors"
	"io"
	"os"
)

type Command struct {
	Input  io.Reader
	Output io.Writer
}

func (c *Command) Run() error {
	if c.Input == nil || c.Output == nil {
		return errors.New("both Input and Output should be non-nil")
	}
	scanner := bufio.NewScanner(c.Input)
	scanner.Scan()
	c.Output.Write(scanner.Bytes())
	return nil
}

func main() {
	c := &Command{}
	fmt.Println(c.Run())
}
```

error処理としてpanicを使用するのは副作用が強すぎるので避けた方が良い (see 付録) 。

## デフォルト値を用意する

errorに変わる手段として、デフォルト値を用意する手法もよく取られる。

```go:上記「ヌルポの例」のデフォルト値処理版
package main

import (
	"bufio"
	"io"
	"os"
)

var DefaultInput io.Reader = os.Stdin
var DefaultOutput io.Writer = os.Stdout

type Command struct {
	Input  io.Reader
	Output io.Writer
}

func (c *Command) input() io.Reader {
	if c.Input != nil {
		return c.Input
	}
	return DefaultInput
}

func (c *Command) output() io.Writer {
	if c.Output != nil {
		return c.Output
	}
	return DefaultOutput
}

func (c *Command) Run() {
	scanner := bufio.NewScanner(c.input())
	scanner.Scan()
	c.output().Write(scanner.Bytes())
}

func main() {
	c := &Command{}
	c.Run()
}
```

例えば [http.Server](https://golang.org/pkg/net/http/#Server) は、 `&http.Server{}` のように使われる想定であることもあり、デフォルト値に関する記載が多い。

## まとめ

Goではzero valueがカジュアルに生成されるので、それらを正しくerror処理するなり、デフォルト値を用意するなりする必要がある。

これは当たり前のようであるが、実際にやってみると、結構泥臭い記述をすることになるため、妥協しがちである。

現在の言語仕様で、この問題をスマートに解決するのは難しそうなので[^2]、筋肉で解決するのが得策だろう[^3]。

## 付録: panicしても良いケース

errorにする方が間違いないことは前提とした上で、panicが使えるケースを考えてみる。

### OK: assersion的用法。例えばMust関数。

有名所で言えば [`regexp.MustCompile`](https://golang.org/pkg/regexp/#MustCompile) や [`template.Must`](https://golang.org/pkg/text/template/#Must) 。
これはassersionの考え方に近い。Mustじゃない版も合わせて用意するべき。

### 許容: 単独の関数で、渡された引数が不正だったケース

例えば [`template.JSEscape`](https://golang.org/pkg/text/template/#JSEscape) 。
引数チェックを呼び出し元に求めているとも言える。

### 微妙: 引数により発生するが、内部状態に依存しているケース

例としては、 [`func (*ServeMux) Handle`](https://golang.org/pkg/net/http/#ServeMux.Handle) 。
patternが既に登録済みの時にpanicすることになっているが、少々危うい仕様。

### Bad: 内部状態だけで起こるケース

上記「ヌルポの例」が該当。引数関係なく、内部状態だけで引き起こされるケース。
recover前提で組むのであれば許容されるかもしれないが、goらしくない実装になる。

## 付録: 冒頭の図のソース

```
@startuml
(*) --> "struct内のZero valueの対処"
if "デフォルト値で処理 vs. エラー処理" then
  -left-> [デフォルト値で処理] "デフォルト値を用意する"
  if "デフォルトを変更可能にするか?" then
    --> [yes] "グローバルにexportedな形で配置する"
    if "nilを入れられた時どうするか"
      --> [error] "安全 :)"
    else
      --> [panic] "ベストではないが妥協ラインではある :("
    endif
  else
    --> [no] "exportしない"
  endif
else
  -right-> [エラー処理] "エラーを適切に処理する"
  if "errorを返す vs. panicにする"
    --> [error] "安心 :)"
  else
    --> [panic] "ドキュメントに記載はするだろうが、あまりよくはない :("
  endif
endif
@enduml
```

[^1]: とはいえ分かり辛いのでなるべく避けたほうが良いと思う。ちなみにObjective-cではnilレシーバへのメッセージ送信は無視される。Rubyでは(method_misingでハックしなければ)NoMethodError。
[^2]: 完全にnull安全である必要はないと思うが、部分的なnon-nil保証は欲しい。 `func (r io.Reader non-nil)`のようなイメージ。
[^3]: 依存の注入について、structのembedを利用する腹案があるので、いずれまとめてみたいと思っている。