Go言語で「なかった」の返し方

何らかの関数が、その結果として「なかった」ということを返す場合について考えてみます。処理自体は成功したが、その結果「なかった」ということを返す場合です。

いくつかのパターンをあげてみます。

## nil

まず始めに `return nil, nil` は **危険** です。エラーでない場合は、何らかの non-nil な値（ポインタ）を返すべきです。

Go言語でコードを書く際はエラーを必ず確認し、そして、以下のような例でエラーでなかった場合は、結果が nil でないことを前提としてコードを書きます。このため `return nil, nil` だと「ぬるぽ（にるぽ？）」です。

```go
result, err := fooFunction()
if err != nil {
    return err
}

// ここに来た場合は result != nil が期待される
result.bar()
```

よってエラーなしの場合は non-nil を返しましょう。
（プライベートな関数ならありかもしれませんが、パッケージ外部に公開するものは気をつけましょう）

戻り値が多値ではない（つまりエラーの戻りがない）場合も同様で、エラーがないということは失敗しない関数だという期待があるので、nil は返さないようにします。
（戻り値がひとつの場合は nil もありかもしれない）

```go
// エラーを返さない関数だから成功するんだよね！？
result := fooFunction()

// ここに来た場合は result != nil が期待される
result.bar()
```

では、non-nil にしつつ、どうやって「なかった」を示すのか、というのを続いて見ていきます。

## エラー

「なかった」をエラー側で返す。

戻り値でエラーを返している関数の場合は「なかった」をエラー側で返すことができます。

[os.Stat](https://golang.org/pkg/os/#File.Stat) ではファイルが存在しない場合はエラーが返ってきます。このとき、ファイルが「なかった」のか、他の何らかのエラーだったのかを区別するために、 [os.IsNotExits](https://golang.org/pkg/os/#IsNotExist) という関数が用意されています。

```go
info, err := os.Stat("foo.txt")
if err != nil {
    if os.IsNotExist(err) {
        // ファイルが「なかった」
        ...
    }
    // その他のエラー
    ...
}
...
```

なお os パッケージには、定数（厳密には変数ですが） ErrNotExist も用意されています。しかし関数を用意しているのは、複数のエラー型（PathError, LinkError, SyscallError）があるためです。

こういった特別な事情がない他のパッケージでは ErrNotExist や ErrNotFound のようなエラー変数がよく使われていると思います。

```go
// ライブラリ側
package foo

// 結果が「なかった」場合
var ErrNotFound = errors.New("not found")
```

```go
// 使う側
ret, err := foo.Bar()
if err != nil {
    if err == foo.ErrNotFound {
        // 「なかった」
        ...
    }
    // その他のエラー
    ...
}
...
```

「なかった」場合と「その他のエラー」を区別したいかどうかは呼び出し元によります。そのため、エラーの一種として返しておいて、区別する手段を用意しておくというのは、扱いを呼び出し元に委ねることができ、合理的でちょうどよい手だと思います。

```go
info, err := os.Stat("foo.txt")
if err != nil {
    // エラーを区別する必要がなければ、そのまま扱うだけ
    ...
}
...
```

## 有無をあらわす bool

map ではキーによるアクセスをしたとき、二つ目の引数で有無を取得できます。

```go
// var data map[string]*Foo だとして
foo, ok := data["hoge"]
```

エラーではなく bool で返したい場合は、この方式も可能です。

## ゼロ値や特別な値

結果がポインタではなく値の場合で、ゼロ値や特別な値を返すパターン。

この場合は nil にはならないので「ぬるぽ」は出ません。

例えば [strings.Index](https://golang.org/pkg/strings/#Index) は文字列のなかに含まれる部分文字列の位置を返す関数です。部分文字列が見つからない場合は `-1` が返ります。

```go
ret := strings.Index("chicken", "dmr")
// ret => -1
```

※ぬるぽは出なくても、戻り値を未検証でこの -1 をスライスで使おうとして panic する可能性はありますが……

`string` を返すような、例えば [path.Split](https://golang.org/pkg/path/#Split) では空の文字列が使われます。

```go
dir, file := path.Split("myfile.css")
// dir => "", file => "myfile.css"
```

エラーの場合とは少し異なりますが、定数で「なかった」場合を定義してもよさそうです。

```go
// ライブラリ側
package foo

// 結果が「なかった」場合
const NotFound = -1

...
```

```go
// 使う側

ret := foo.Bar()
if ret == foo.NotFound {
   ...
}
...
```

## 「なかった」を判定できるオブジェクト

値ではなくポインタで何らかの `struct` を結果に返す場合で「なかった」を扱いたい場合は、その `struct` に判定用のメソッド（IsEmpty や IsZero など）を用意する手もあります（これはポインタでも値でも、どちらの場合でも使えます）。

```go
type Result struct {
    Value int
    Foo   string
    Bar   string
}

func (r *Result) IsEmpty() bool {
    return r.Value == -1 // 「なかった」場合に -1 が入るとしたら
}
```

```go
result := fooFunction()
if result.IsEmpty() {
    // 結果が「なかった」場合
    ...
}

// 結果が「あった」場合
...
```

ただし、このパターンはさほど見かけません。

呼び出し元が IsEmpty で判定しないコードを書いても、処理が継続してよいような場所ではありだと思いますが、呼び出し元が判定をサボった時に panic なり困った自体が起きるようであれば、エラーを使って「チェックを強制」しましょう。

## SQLパッケージの例

sql パッケージは、クエリの結果としてハンドラー的なオブジェクトが返ってきて、それを経由して実際の行を取得するようになっています。

[sql.Query](https://golang.org/pkg/database/sql/#DB.Query) では [sql.Rows](https://golang.org/pkg/database/sql/#Rows) が返ってきます。これ自体は、SQLの結果がゼロ行であっても non-nil です。結果が「なかった」場合は sql.Rows.Next が false を返すので、処理のループに入りません。

```go
rows, err := db.Query("SELECT name FROM users")
if err != nil {
    return nil, err
}

// rows != nil
defer rows.Close()

// 結果がゼロ行の場合は Next が false
for rows.Next() {
    var name string
    err = rows.Scan(&name)
    ...
}
...
```

[sql.QueryRow](https://golang.org/pkg/database/sql/#DB.QueryRow) では [sql.Row](https://golang.org/pkg/database/sql/#DB.QueryRow) が返ってきます。これも結果がゼロ行でも non-nil です。結果が「なかった」場合は Scan がエラー sql.ErrNoRows を返します。

```go
row := db.QueryRow("SELECT name FROM users WHERE id = ?", id)

var name string
err := row.Scan(&name)
if err != nil {
    if err != sql.ErrNoRows {
        // 何らかのエラー
        return nil
    }
    // err == sql.ErrNoRows なので、結果が「なかった」
    ...
}
...
```

SQLの場合は上記のようにして「なかった」のかエラーなのか、区別できるようになっています。

## まとめ

原則として:

- nil, nil はダメ。エラーでなければ何らかの値かオブジェクトを返そう

パターンとして:

- エラーの一種で「なかった」を返す
- ゼロ値や特別な値
- 結果のオブジェクトが「なかった」を表現する
- 結果と取り出すデータが別になっている

いろいろありますね。

2018年も、楽しく Go を書きましょう！