ゼロ値を使おう #golang

## Goの変数は必ず初期化される

### 組込み型のゼロ値

Goの変数は必ず初期化されることをご存知でしょうか？
例えば、`int`型の場合は`0`で、`string`型の場合は`""`で初期化されています。

次のように、代入せずに`fmt.Printf`で表示させてみましょう。

```go
package main

import "fmt"

func main() {
  var n int
  var f float64
  var s string
  var b bool
  fmt.Printf("%#v %#v %#v %#v", n, f, s, b)
}
```

このコードを[実行する](https://play.golang.org/p/ttwnZa9CdND)と次のような結果が表示されます。

```
0 0 "" false
```

組込み型のゼロ値をまとめると次のようになります。

|型|ゼロ値|
|----|------|
|`int`,`int8`,`int16`,`int32`,`int64`|`0`|
|`uint`,`uint8`,`uint16`,`uint32`,`uint64`|`0`|
|`byte`,`rune`,`uintptr`|`0`|
|`float32`,`float64`|`0`|
|`complex64`,`complex128`|`0`|
|`bool`|`false`|
|`string`|`""`|
|`error`|`nil`|

### コンポジット型のゼロ値

配列や構造体などの複数のデータ型が集まったコンポジット型のゼロ値について考えます。

配列のゼロ値は、すべての要素がゼロ値である配列です。
例えば、次のように要素が`3`で`int`型配列のゼロ値はすべての要素が`0`である配列になります。

```go
package main

import "fmt"

func main() {
  var ns [3]int
  fmt.Println(ns)
}
```

このコードを[実行する](https://play.golang.org/p/jfk_ub31EdV)と次のような結果が表示されます。

```
[0 0 0]
```

一方、構造体のゼロ値はすべてのフィールドがゼロ値である値です。
例えば、次のように`int`型のフィールド`N`と`int`型の配列で要素数が3のフィールド`NS`がある場合は、
それぞれがゼロ値で初期化された構造体の値になります。

```go
package main

import "fmt"

func main() {
  type Hoge struct {
    N  int
    NS [3]int
  }

  var h Hoge
  fmt.Printf("%#v", h)
}
```

このコードを[実行する](https://play.golang.org/p/cw_9dex5R3U)と次のような結果が表示されます。

```
main.Hoge{N:0, NS:[3]int{0, 0, 0}}
```

なお、構造体の埋め込みについてもフィールド名の無い匿名フィールドであって、フィールドであることには変わらないため、次のように明示的に初期化しなくてもゼロ値になります。

```go
package main

import "fmt"

func main() {
  type Hoge struct {
    N int
  }

  type Fuga struct {
    int
    Hoge
  }

  var f Fuga
  fmt.Printf("%#v", f)
}
```

このコードを[実行する](https://play.golang.org/p/AFzScMTwM9l)と次のような結果が表示されます。

```
main.Fuga{int:0, Hoge:main.Hoge{N:0}}
```

### ゼロ値が`nil`である型

組込み関数の`make`で初期化を行う型はゼロ値が`nil`です。
スライス、チャネル、マップなどがそれに該当します。

```go
package main

import "fmt"

func main() {
  var ch chan int
  var ns []int
  var m map[string]int
  fmt.Printf("%#v %#v %#v", ch, ns, m)
}
```

このコードを[実行する](https://play.golang.org/p/LZAGhJ6LpEH)と次のような結果が表示されます。

```
(chan int)(nil) []int(nil) map[string]int(nil)
```

`make`関数で初期化する型以外でもいくつかゼロ値が`nil`である型が存在します。

```go
package main

import "fmt"

func main() {
  var f func()
  var ptr *int
  fmt.Printf("%#v %#v", f, ptr)
}
```

このコードを[実行する](https://play.golang.org/p/uFKrOmWI4jz)と次のような結果が表示されます。

```
(func())(nil) (*int)(nil)
```

なお、The Go Playgroundで実行すると次のように、`go vet`がエラーを出しますがここでは気にしなくても良いです。

```
prog.go:8: Printf format %#v arg f is a func value, not called
```

## ゼロ値のまま扱う

### 組込み型をゼロ値のまま扱う

次のように、`int`型を使ってカウントを行う際や合計を求める際に初期値として`0`を設定するコードを見かけることがあります。

```go
package main

import "fmt"

func main() {
  count := 0
  count++
  count++
  fmt.Println(count)
}
```

しかし、`int`型の変数はわざわざ`0`を代入して初期化を行わなくてもゼロ値である`0`で初期化されています。
そのため、次のように変数定義を行うだけで問題ありません。

```go
package main

import "fmt"

func main() {
  var count int
  count++
  count++
  fmt.Println(count)
}
```

ゼロ値という概念があることは非常に重要で強力です。
次のように`int`型を値として扱うマップを考えると、その効果を感じることができるでしょう。

```go
package main

import "fmt"

func main() {
  words := []string{"dog", "cat", "dog", "fish", "cat"}
  wc := map[string]int{}
  for _, w := range words {
    wc[w]++
  }
  fmt.Println(wc)
}
```

このコードを[実行する](https://play.golang.org/p/oSdBs2MDEc-)と次のような結果が表示されます。

```
map[dog:2 cat:2 fish:1]
```

変数`wc`は単語ごとのカウントを取るための変数です。
キーが単語でバリューがその単語の出現回数となっています。

変数`wc`は最初に空のマップが代入され、その後は特にキー毎に初期値を入れるような処理はされていません。
Goのマップはキーが存在しない場合にゼロ値を返すため、明示的な初期化が必要ないからです。

`wc["dog"]`のようにアクセスした場合に、マップにキー`"dog"`が存在していない場合はゼロ値が返ってきます。
`int`型のゼロ値は`0`であるため、そこに`wc["dog"]++`のように加算を行っても問題はありません。

`bool`型についてもゼロ値である`false`を上手く使うことで無駄な初期化を省いてシンプルに記述することができます。

次の例では、マップのバリューに`bool`型を使って単語リスト中に単語が存在するかを表す変数`wc`を定義しています。
前述の通り、マップはキーが存在しない場合にバリューの型のゼロ値を返すため、`bool`型の場合は`false`になります。
これをうまく使うことで、マップにキーが存在しないという状態をゼロ値（`false`）で表すことが可能になっています。

```go
package main

import "fmt"

func main() {
  words := []string{"dog", "cat", "dog", "fish", "cat"}
  wc := map[string]bool{}
  for _, w := range words {
    wc[w] = true
  }

  for _, w := range []string{"dog", "pig"} {
    if wc[w] {
      fmt.Println(w, "は存在する")
    } else {
      fmt.Println(w, "は存在しない")
    }
  }
}
```

### スライスをゼロ値のまま扱う

スライスについてもゼロ値のまま扱えるように設計されています。
スライスは組込み関数である`len`や`cap`、`append`などの引数に渡すことができます。
これらの関数では、次のように引数のスライスがゼロ値である`nil`でも動作するように実装されています。

```go
package main

import "fmt"

func main() {
  var ns []int
  fmt.Printf("%#v %d %d\n", ns, len(ns), cap(ns))
  ns = append(ns, 10, 20)
  fmt.Println(ns)
}
```

このコードを[実行する](https://play.golang.org/p/_bMqY1HnQxa)と次のような結果が表示されます。

```
[]int(nil) 0 0
[10 20]
```

### ゼロ値で扱える型

Goではゼロ値のまま扱えるように工夫されている型があります。
例えば、`sync`パッケージで定義されている`sync.Mutex`型は、
次のようにゼロ値のまま使えるように作られています。

```go
package main

import "sync"

func main() {
  var mu sync.Mutex
  mu.Lock()
  mu.Unlock()
}
```

また、`sync`パッケージで提供されている多くの型はゼロ値で使えるようになっています。
`sync.WaitGroup`も次のようにゼロ値で使えるように作られています。

```go
package main

import (
  "fmt"
  "sync"
)

func main() {
  var wg sync.WaitGroup

  wg.Add(2)

  go func() {
    defer wg.Done()
    fmt.Println("done 1")
  }()

  go func() {
    defer wg.Done()
    fmt.Println("done 1")
  }()

  wg.Wait()
  fmt.Println("main done")
}
```

## ゼロ値のまま扱えるようにする

### 必要な時に初期化を行う

`sync`パッケージで用意されている型のように、ゼロ値にままで扱えるようにするためには少し工夫が必要です。
ゼロ値で扱えるようにするということは、`New`のプリフィックスが付くような初期化用の関数を用意せずに初期化をする必要があります。

例えば、次のような単語をカウントする型である`WordCounter`型を実装することを考えてみましょう。
`WordCounter`型は内部にマップを持ちます。
マップはゼロ値（`nil`）のまま扱うことができないため、初期化をする必要があります。

マップの初期化は`NewWordCounter`関数で行い、戻り値として`*WordCounter`型の値を返します。
また、`*WordCounter`型はメソッドとして単語ごとの出現数のカウントを行う`Count`メソッドと
単語ごとの出現数を返す`Get`メソッドを持ちます。

```go
package main

import "fmt"

type WordCounter struct {
  m map[string]int
}

func NewWordCounter() *WordCounter {
  return &WordCounter{
    m: map[string]int{},
  }
}

func (wc *WordCounter) Count(s string) int {
  wc.m[s]++
  return wc.m[s]
}

func (wc *WordCounter) Get(s string) int {
  return wc.m[s]
}

func main() {
  wc := NewWordCounter()
  words := []string{"dog", "cat", "dog", "fish", "cat"}
  for _, w := range words {
    wc.Count(w)
  }
  fmt.Println("dog", wc.Get("dog"))
}
```

このコードを[実行する](https://play.golang.org/p/iKV1AYB9vhr)と次のような結果が表示されます。

```
dog 2
```

ゼロ値で扱えるようにするためには、`NewWordCounter`関数で行っていた処理を各メソッドで行う必要があります。
例えば、最も単純な方法としては次のように、関数の先頭でフィールドが初期化されているか確認して初期化を行うというものがあります。

```go
func (wc *WordCounter) Count(s string) int {
  if wc.m == nil {
    wc.m = map[string]int{}
  }
  wc.m[s]++
  return wc.m[s]
}
```

しかし、この方法では初期化が複雑になった場合や初期化するフィールドが複数存在する場合には、コードが煩雑になってしまいます。
そこで、次のように`sync.Once`型を使い、初期化を一度だけ行うようにすると初期化コードをスッキリと書くことができます。

```go
package main

import (
  "fmt"
  "sync"
)

type WordCounter struct {
  initOnce sync.Once
  m        map[string]int
}

func (wc *WordCounter) init() {
  wc.m = map[string]int{}
}

func (wc *WordCounter) Count(s string) int {
  wc.initOnce.Do(wc.init)
  wc.m[s]++
  return wc.m[s]
}

func (wc *WordCounter) Get(s string) int {
  wc.initOnce.Do(wc.init)
  return wc.m[s]
}

func main() {
  var wc WordCounter
  words := []string{"dog", "cat", "dog", "fish", "cat"}
  for _, w := range words {
    wc.Count(w)
  }
  fmt.Println("dog", wc.Get("dog"))
}
```

`*WordCounter`型のメソッドとして新たに`init`メソッドが追加されています。
また、フィールドとして`sync.Once`型の`initOnce`フィールドも追加されています。

`sync.Once`型は、`Do`メソッドを呼び出しを1度だけに限定している型です。
`Do`メソッドに渡した関数を1度だけ実行し、その後、いくら`Do`メソッドを呼び出しても関数は実行されることはありません。

そのため、`Count`メソッドや`Get`メソッドの先頭で、`wc.initOnce.Do(wc.init)`のように呼び出すことで、
初期化を一度だけ実行してやることができます。

なお、`sync.Once`型もゼロ値で扱えるように設計されているため、明示的な初期化が必要ありません。
このように、うまくゼロ値で扱えるようにすると他の型からも利用しやすくなります。

### ゼロ値とメソッド

ゼロ値で扱えるような構造体型を作った場合に、メソッドのレシーバはポインタにすべきでしょうか？

[Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-type)にもあるように、基本的にはレシーバはポインタにすべきです。
もちろん、ゼロ値で扱えるようにした構造体型についても同様です。

`*T`型をレシーバに持つメソッドを定義した場合に、`T`型の変数を使っても簡単に呼び出せれるように工夫がされています。
例えば、`*T`型にメソッド`M`が定義されている場合に、`T`型の変数`t`を使って、`t.M()`のように呼び出すことが可能です。
この場合、`t.M()`は`(&t).M()`と記述したように扱われます。

実際に、前述のコードでは次のように`WordCounter`型の変数に対して、`*WordCounter`型をレシーバに取るメソッドである、`Count`メソッドと`Get`メソッドを呼び出しています。

```go
func main() {
  var wc WordCounter
  words := []string{"dog", "cat", "dog", "fish", "cat"}
  for _, w := range words {
    wc.Count(w) // (&wc).Count(w)と同様
  }
  fmt.Println("dog", wc.Get("dog")) // (&wc).Get("dog")と同様
}
```

しかし、いくら簡単に呼び出せるように記述できるからといって、`T`型のメソッドセットには、`*T`型のメソッドセットは含まれていません（[言語仕様](https://golang.org/ref/spec#Method_sets)で定義されています）。
そのため、`*T`型がインタフェースである`I`型を実装している場合、`T`型は実装していることにはなりません。

この話は、次のようにゼロ値で扱える`byte.Buffer`型の値を`io.Writer`や`io.Reader`として渡す際に出会うことが多いでしょう。

```go
var buf bytes.Buffer

// bufはio.Writerを実装していないからコンパイルエラー
fmt.Fprintf(buf, "Hello")

// &bufはio.Writerを実装している
fmt.Fprintf(&buf, "Hello")
```

## まとめ

この記事ではゼロ値について基礎的な話からゼロ値で扱える型の設計方法まで解説を行いました。
うまくゼロ値で扱うことによってシンプルでスッキリとしてコードになるでしょう。

ぜひ読者のみなさんもゼロ値を正しく理解し、積極的に使ってみてください。