Goを学びたての人が誤解しがちなtypeと構造体について #golang

## はじめに

タイトルをキャッチーかつ若干煽り気味にしたのは、そもそも記事を見てもらう確率を上げるためで他意はありません。なぜ読んでほしいのかというと、`type`と`struct`について、一部の機能に着目して、それが`type`と`struct`の全てだと誤解されるとマズいなと感じることが最近多いためです。

この記事で書いてあることは過去にも[インタフェースの実装パターン](http://qiita.com/tenntenn/items/eac962a49c56b2b15ee8)や[2016年度Go研修](http://www.slideshare.net/takuyaueda967/2016-go)で取り上げていますが、今回は端的に分かりやすくなるようにまとめたいと思います。

## 構造体

構造体とは何でしょうか？[言語仕様](https://golang.org/ref/spec#Struct_types)を見ると、名前と型を持つフィールドというもの集まりだと書かれています。構造体型は、`struct`という予約語を使って定義することができます。

```go
struct {
    Name string
    Age  int
}
```

上記の場合だと、`string`型の`Name`フィールドと`int`型の`Age`フィールドを持つ構造体を表しています。
Goを勉強したことがある方は、なんで`type`と書かないんだろうと思うかもしれませんが、`type`の話と`struct`の話は別の概念の話なので、ここでは別で扱います。

構造体型の変数は以下のように定義することができます。

```go
var person struct {
    Name string
    Age  int
}
```

初期化したい場合は、以下のように書くことができます。

```go
var person struct {
    Name string
    Age  int
}{
    Name: "tenntenn",
    Age:  30,
}
```

フィールドへの代入は`.`を使ってアクセスします。

```go
person.Name = "Takuya Ueda"
```

また、構造体のフィールドには`struct`タグが設けれたり、名前のない匿名フィールドの埋め込みなんかもありますが、ここでは話がややこしくなるので解説するのはやめておきます。詳しく知りたい型は、言語仕様を読むか、[インタフェースの実装パターン](http://qiita.com/tenntenn/items/eac962a49c56b2b15ee8)を読んで頂けると幸いです。

さて、`type`やメソッドの話は構造体の話では出てきません。なぜなら、構造体と`type`、メソッドの話は別の概念だからです。
次に`type`の解説をしましょう。

## type

`type`という予約語を用いると、既存の型や型リテラルに別名をつけることができます。[言語仕様](https://golang.org/ref/spec#Type_declarations)にも同じようなことが書いてあります。

ここで注意してほしいのは、`type`は決して、構造体だけのために用いられるものではないということです。初学者向けのサイトや書籍では、構造体の定義方法を`type`有りきで説明しがちです。確かに間違ってはいないし、他の言語の`class`の概念と対比できて、分かりやすいかもしれません。しかし、私はあまりその説明方法を好みません。なぜなら、`type`は構造体型以外の型にも名前をつけることができるし、そこからつながるメソッドやインタフェースについても、構造体という制限はないからです。

言語仕様を確認することが、言語を学習する上でもっとも正しく理解できる方法だと思います。言語仕様に書いてある`type`の文法を見てましょう。

```go
TypeDecl     = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec     = identifier Type .
```

`type`という予約語の後ろに`TypeSpec`というものが来ています。この`TypeSpec`は何なのかというと、`identifier`の後ろに`Type`を記述したものです。`identifier`は識別子を表すので、ここでは新しくつける型名にあたります。それでは`Type`とは具体的に何にあたるのでしょうか？

[言語仕様](https://golang.org/ref/spec#Type)から`Type`の定義を調べてみましょう。

```go
Type      = TypeName | TypeLit | "(" Type ")" .
TypeName  = identifier | QualifiedIdent .
```

上記の定義から、`TypeName`または、`TypeLit`、そして、`()`で括られた`Type`ということが分かります。`TypeName`は、`identifier`と`QualifiedIdent`なので、識別子、つまりはすでに名前の付いている型を表します。ちなみに、`QualifiedIdent`は、以下のように定義されているので、別のパッケージの型を参照する際に用いられる記法です（この場合は型だが、変数や関数の場合もある）。

```go
QualifiedIdent = PackageName "." identifier .
```

さて、ここまでで`type`で新しく名前が付けられる型として、構造体だけではなく、既存の型にも別名が付けられることが、言語仕様を見ることで分かりました。つまり、以下のような記述ができるということです。

```go
type Hex int
```

これは、`int`型に`Hex`という別名を付けています。なお、`int`と`Hex`のように`type`で別名をつけた場合、キャストなしでは各型をまたぐ演算や変数へ代入ができません。

もちろん、別のパッケージの型にも新たに名前をつけることができます。

```go

type MyReader io.Reader
```

文法をきちんと確認すると、普段使ってなくて知らないことに気付かされます。

自明な`"(" Type ")"`は置いておくとして、`Type`にはもう一つ、`TypeLit`という記述ができることが文法上から分かっています。`TypeLit`の定義を見てみましょう。

```go
TypeLit   = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
            SliceType | MapType | ChannelType .
```

どうやら色んな型の記述であることが分かります。`TypeLit`が何を表すのかというと、型リテラルというものを表します。型リテラルは型の情報そのものを記述する方法で、よく知られているのは以下のスライスやマップの定義でしょう。

```go
var ns []int
var m map[string]int
```

上記の`TypeLit`の定義を見る限り、構造体や配列、ポインター、関数、インタフェース、チャネルなども同じように型リテラルで書くことができます。

ここで察しが良い方は、構造体の変数定義を

```go
var person struct {
    Name string
    Age  int
}
```

のように書ける理由が分かったでしょう。文法上では、構造体の型リテラルもスライスの型リテラル（`[]int`など）も同じように`TypeLit`で扱われ、さらに`Type`でまとめられています。たとえば、上記の例で出てきている変数宣言の`var`についても、以下のように定義されています。

```
VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec     = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
```

1つ以上の識別子の後ろに`Type`を書くということが文法上で定義されています。

`type`の方に話を戻すと、`type`で別名を定義できるものとして、以下の4つがあることが分かりました。

* 組み込み型（intやfloat64）など
* パッケージ内の型（`type`で定義されたもの）
* パッケージ外の型（`type`で定義され、パッケージ名を指定して記述するもの）
* 型リテラル

なお、文法上は組み込み型とパッケージ内の型については区別がありません。ここまでの説明で、構造体と`type`はセットで覚えるものとすることが、あまり良いことではないことがお分かりいただけたかと思います。

構造体を学習する上で、他の言語の`class`に関連付けてメソッドについても一緒に扱うことが多いようです。メソッドについても構造体ありきで覚えてしまうと非常にもったいので、簡単に説明します。

## メソッド

メソッドは、関数定義に加えて、名前の前にレシーバというものを記述します。

```go
func (h Hex) String() string {
    return fmt.Sprint("%x", int(h))
}
```

このレシーバにできる型は、以下のような決まりがあります。

* `type`で名前が付けられている型はOK
* 上記の型のポインタ型はOK
* 組み込み型はダメ
* パッケージ外の型はダメ
* 型リテラルはダメ

つまり、パッケージ内で`type`定義された型のみがレシーバにできます。

ここで思い出してほしいのは、`type`で別名が付けれる型についてです。`type`で新たに名前が付けれるのは以下の4つでした。

* 組み込み型（intやfloat64）など
* パッケージ内の型（`type`で定義されたもの）
* パッケージ外の型（`type`で定義され、パッケージ名を指定して記述するもの）
* 型リテラル

つまり、上記の4種類については、`type`で新たに型名さえつければ、メソッドのレシーバとして使用することができます。

そのため、以下のようなこともできます。

```go
type Hex int
func (h Hex) String() string {
    return fmt.Sprint("%x", int(h))
}
```

また、もちろん関数にもメソッドを付けれます。

```go
type Func func() string
func (f Func) String() string {
     return f()
}
```

スライスやチャネルなどの型リテラルにも、`type`で別名を付けさえすれば、メソッドを設けることができます。
そして、ここでは詳しく触れませんが、Goのインタフェースはメソッドの集まりで、あるインタフェースのメソッドリストが、ある型のメソッドリストに内包する形になっていれば、その型はそのインタフェースを実装したことになります。つまりは、構造体以外もメソッドが設けられるのえ、インタフェースを実装することができるということです。関数にインタフェースを実装させるのは、[http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc)のように、標準パッケージでも多く使われているので、上記の`type`やメソッドの性質を理解しておくのは、インタフェースや標準パッケージを理解する上で重要です。

## おわりに

`type`やメソッドが構造体ありきで学習していくと、後々、[http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc)などに出くわしたときに、きちんと理解できず、「おまじない」になってしまいがちです。言語仕様をきちんと読み、普段なんとなく書いている記述が文法上どのように定義されているのか理解することで、今まできちんと理解してなかった概念や知らなかった概念を知る機会になります。Goの[言語仕様](https://golang.org/ref/spec)は、そんなに長くなく、少しずつ読んでいけばちゃんと読める量なので、ぜひ読んでみることをオススメします。英語が厳しい方は、文法を定義したBNFを斜め読みするだけでも十分効果はあると思います。