The Go Programming Language Specificationの翻訳、5回目です。
前回までの訳はGo言語仕様[日本語訳]にまとめてあります。


型は、その型を持つ値に対し具体的な値と操作の組み合わせを規定します。型を表すには型の(パッケージ名を伴なう場合もある)名称(§限定付き識別子、 §型の宣言) または型リテラルで記述します。事前に定義した型から別の新しい型を作成することもできます。

Type      = TypeName | TypeLit | "(" Type ")" .
TypeName  = QualifiedIdent.
TypeLit   = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
	    SliceType | MapType | ChannelType .

論理値型、数値型、文字列型として事前宣言済みの型が用意されています。 コンポジット型(配列、構造体、ポインタ、関数、インタフェース、スライス、マップ、チャネル型)は型リテラルを使って作られます。

型は、その型と関連付けられたメソッド群を持ちます (§インタフェース型、§メソッドの宣言) 。インタフェース型のメソッド群はインタフェース自身です。インタフェース以外の型を仮にTとすると、そのT型のメソッド群はT型のレシーバを持ったすべてのメソッドです。 ポインタ型*Tと対応するメソッド群は、*TまたはT型のレシーバを持ったすべてのメソッドです。(すなわち、T型が持つメソッド群が含まれます。)また各メソッドは、メソッド群内におけるユニークな名前を持っています。

変数の静的な型(または適正な型)は、変数の宣言時に指定された型です。インタフェース型の変数は、他の型とは違って動的な型を持っており、実行時にその変数に格納された値の型が実際の型となります。この動的な型はプログラム実行中に値が入れ替わったとしても、常にインタフェース変数の静的な型と互換のある値が代入されます。非インタフェース型において、動的な型と静的な型は常に一致します。

論理値型

論理値型は、事前宣言済み定数trueまたはfalseによる論理値を表現します。論理値型として事前宣言済みの型はboolです。

数値型

数値型は、整数または浮動小数点の値を表現します。アーキテクチャに依存しない数値型として事前宣言済みの型は、

uint8    符号なし  8-ビット 整数 (0 to 255)
uint16   符号なし 16-ビット 整数 (0 to 65535)
uint32   符号なし 32-ビット 整数 (0 to 4294967295)
uint64   符号なし 64-ビット 整数 (0 to 18446744073709551615)

int8     符号あり  8-ビット 整数 (-128 to 127)
int16    符号あり 16-ビット 整数 (-32768 to 32767)
int32    符号あり 32-ビット 整数 (-2147483648 to 2147483647)
int64    符号あり 64-ビット 整数 (-9223372036854775808 to 9223372036854775807)

float32  IEEE-754 32-ビット 浮動小数値
float64  IEEE-754 64-ビット 浮動小数値

byte     uint8の別名

整数型は一般的なバイナリ形式であり、「n-ビット 整数」型の値はnビットのサイズを持ち、負の値は絶対値を2の補数で表現します。

実装に依存したサイズを持つ数値型も用意されています。

uint     32 または 64 ビット
int      32 または 64 ビット
float    32 または 64 ビット
uintptr  ポインタの値をそのまま格納するのに充分な大きさの符号なし整数

uint8の別名であるbyte型を除いて、数値型はサイズが同じであってもそれぞれが別の型です。これは移植時に問題が起きないようにするためです。互換性のない数値型どうしを式や代入に使用するときは変換が必要となります。たとえばint32intが、あるアーキテクチャ上で同一のサイズであったとしても、これらは同じ型ではありません。

文字列型

文字列型は文字列の値を表現します。文字列はbyteの配列のように振舞いますが、値は不変です。つまり一度作成された以降は、文字列の内容を変更できません。文字列型として事前宣言済みの型はstringです。

文字列型の要素はbyte型データを持っているため、一般的なインデックスを使ったアクセスが可能です。ただし要素のアドレスを取得することはできません。すなわちs[i]が、ある文字列のi番目のバイトであるとして、&s[i]とすることはできません。文字列の長さは、組み込み関数lenを使用して調べることができます。文字列sがリテラルであれば、文字列長はコンパイル時に定数となります。

配列型

配列は同一の型(要素型)を持つ要素を並べたものです。要素の数は長さ(length)と呼ばれます。この値はマイナス値には成り得ません。

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

配列の長さ情報は、その配列型の一部であり定数式で使うことができます。配列aの長さは、組み込み関数len(a)を使用して調べることができ、コンパイル時に定数となります。配列の要素は、0からlen(a)-1までの整数によるインデックスで指し示すことができます(§インデックス)。

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64

スライス型

スライスはある配列内の連続した領域への参照であり、スライスの内容はその配列の要素の並びです。スライス型は、その要素型を持つ配列すべてのスライスの集合を表します。スライス型の値はnilをとることがあります。

SliceType = "[" "]" ElementType .

配列のように、スライスはインデックスによる指定が可能で長さを持ちます。スライスsの長さは、組み込み関数len(s)を使用して調べることができますが、配列の場合とは異なり長さは実行中に変わることがあります。要素は0からlen(s)-1までの整数によるインデックスで指し示すことができます(§インデックス)。

スライスは一度初期化されると、その要素を所有する配列との関連を常に保ちます。そのため、スライスは元となった配列および、同一の配列から作られた別のスライスとメモリを共有します。これとは対照的に、異なる配列は常に異なるメモリ領域を有します。

スライスの元になった配列は、スライスの最後の要素以降にも要素を持つことがあります。キャパシティとは範囲の大きさであり、スライスの長さと、元の配列のスライス以降の長さとの合計です。スライスから新しく『スライスする』ことによって、最大でキャパシティの値までの長さのスライスを作ることができます(§スライス)。スライスaのキャパシティは組み込み関数cap(a)で調べることができます。len()cap()の関係は次のとおりです。

0 <= len(a) <= cap(a)

初期化されていないスライスの値はnilです。nilスライスの長さとキャパシティはともに0です。初期化済みの新しいスライスを作るには組み込み関数makeを使用します。make関数はパラメータにスライスの型と長さ、オプションでキャパシティをとります。

make([]T, length)
make([]T, length, capacity)

make()は隠された配列を新たに割り当て、それを参照するスライスを返します。次のようにします。

make([]T, length, capacity)

これは配列を割り当て、そこからスライスを作成するのと同じことなので、ゆえに次の2つの例は結果として同じスライスになります。

make([]int, 50, 100)
new([100]int)[0:50]

構造体型

構造体は、フィールドと呼ばれる要素の集まりで、それぞれが名前と型を持っています。フィールド名は明示的(IdentifierList)、または暗黙的(AnonymousField)に指定されます。ブランクフィールドを除いて、フィールド名は構造体内でユニークである必要があります。

StructType     = "struct" "{" [ FieldDeclList ] "}" .
FieldDeclList  = FieldDecl { ";" FieldDecl } [ ";" ] .
FieldDecl      = (IdentifierList Type | AnonymousField) [ Tag ] .
AnonymousField = [ "*" ] TypeName .
Tag            = StringLit .
// 空の構造体
struct {}

// 6フィールド持つ構造体
struct {
	x, y int;
	u float;
	_ float;  // パディング
	A *[]int;
	F func();
}

フィールドが型だけでフィールド名を指定せずに宣言されたときは匿名フィールドとなります。このような匿名フィールドの型は、型名Tまたは型へのポインタ*Tのように記述しなければなりません。T自体はポインタ型でないかもしれません。フィールド名を伴わないとき型名がフィールド名として扱われます。

// 匿名フィールド T1, *T2, P.T3, *P.T4を持つ構造体
struct {
	T1;        // フィールド名は T1
	*T2;       // フィールド名は T2
	P.T3;      // フィールド名は T3
	*P.T4;     // フィールド名は T4
	x, y int;  // フィールド名は x と y
}

下の宣言は、構造体内でフィールド名がユニークにならないため誤りです。

struct {
	T;         // 匿名フィールド *T と *P.T で不整合
	*T;        // 匿名フィールド  T と *P.T で不整合
	*P.T;      // 匿名フィールド  T と *T   で不整合
}

匿名フィールド内のフィールドとメソッド(§メソッドの宣言)は、その構造体直管のフィールドとメソッドへ昇格されます($セレクタ)。仮にS構造体とT型があるとすると、以下のルールが成り立ちます。

  • Sが匿名フィールドとしてTを有していれば、Sのメソッド群はTのメソッド群を含みます。
  • Sが匿名フィールドとして*Tを有していれば、Sのメソッド群は*Tのメソッド群(Tのメソッド群も含む)を含みます。
  • Sが匿名フィールドとしてTまたは*Tを有していれば、*Sのメソッド群は*Tのメソッド群(Tのメソッド群も含む)を含みます。

フィールドの宣言には、オプションで文字列リテラルを使ってタグを指定することができます。タグはそれが記述されているフィールド宣言の全フィールドの属性となります。タグはリフレクションインタフェースを使って参照できますが、それ以外は無視されます。

// TimeStampプロトコルバッファと一致した構造体
// タグの文字列でプロトコルバッファのフィールド番号を定義
struct {
	microsec  uint64 "field 1";
	serverIP6 uint64 "field 2";
	process   string "field 3";
}

ポインタ型

ポインタ型は所定の型(ベース型)の変数へのすべてのポインタの集合を表します。ポインタの値はnilをとることがあります。

PointerType = "*" BaseType .
BaseType = Type .
*int
*map[string] *chan int

関数型

関数型は、同一のパラメータと同一の戻り値を持つすべての関数の集合を表します。関数型の値はnilをとることがあります。

FunctionType   = "func" Signature .
Signature      = Parameters [ Result ] .
Result         = Parameters | Type .
Parameters     = "(" [ ParameterList ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] ( Type | "..." ) .

名前(IdentifierList)はパラメータリストまたは戻り値リストすべてに記述するか、またはすべて省略するかいずれかです。記述した場合、それぞれの名前がアイテム(パラメータまたは戻り値)ひとつを表します。省略した場合、それぞれの型がその型のアイテムひとつを表します。パラメータと結果リストは常に括弧でくくられます。例外として、戻り値がひとつだけで、名前がなく、括弧なしで記述された関数型でないときだけは括弧は不要です。

最後のパラメータだけは型の代わりに…と記述可能です。これは、この関数がゼロ個以上の任意の型の付加的パラメータを処理することを表します。

func ()
func (x int)
func () int
func (string, float, ...)
func (a, b int, z float) bool
func (a, b int, z float) (bool)
func (a, b int, z float, opt ...) (success bool)
func (int, int, float) (float, *[]int)
func (n int) (func (p* T))

インタフェース型

インタフェース型はメソッド群を規定します。メソッド群はインタフェース名で呼ばれます。インタフェース型の変数には、そのインタフェースの「全スーパーセットのメソッド群を持っている型」の値を格納することができます。そのような型はこのインタフェースを実装していると言えます。インタフェース型の値はnilをとることがあります。

InterfaceType      = "interface" "{" [ MethodSpecList ] "}" .
MethodSpecList     = MethodSpec { ";" MethodSpec } [ ";" ] .
MethodSpec         = MethodName Signature | InterfaceTypeName .
MethodName         = identifier .
InterfaceTypeName  = TypeName .

他のメソッド群でも同じですが、インタフェース型においてメソッドはユニークな名前を持つ必要があります。

// 単純なFileインタフェース
interface {
	Read(b Buffer) bool;
	Write(b Buffer) bool;
	Close();
}

インタフェースは複数の型に実装することができます。例えば、S1S2 の2つの型が次のメソッド群を持つ場合、

func (p T) Read(b Buffer) bool { return ... }
func (p T) Write(b Buffer) bool { return ... }
func (p T) Close() { ... }

(TS1S2を表すとして)File インタフェースはS1S2の両方に実装されます。S1S2が他のメソッドを持つか共有していても関係ありません。

型は、その型のメソッド群の一部から構成されているインタフェースすべてを実装していることになります。したがって複数の異なるインタフェースを実装することもできます。例を挙げるなら、すべての型は次の空(empty)インタフェースを実装しています。

interface{}

同様に、下のインタフェースの記述方法をみてください。ここでは型の宣言内でLockという名のインタフェースを定義しています。

type Lock interface {
	Lock();
	Unlock();
}

S1S2がこれらのメソッドを実装した場合、

func (p T) Lock() { ... }
func (p T) Unlock() { ... }

S1S2Fileインタフェースと同様にLockインタフェースも実装します。

インタフェースにはメソッドを記述する代わりに、別のインタフェース型を含むことができます。仮に Tというインタフェース名を記述したとすると、これは Tのメソッドを明示的に列挙したのと同じことになります。

type ReadWrite interface {
	Read(b Buffer) bool;
	Write(b Buffer) bool;
}

type File interface {
	ReadWrite;  // ReadWrite内のメソッドを列挙したことと同じ
	Lock;       // Lock内のメソッドを列挙したことと同じ
	Close();
}

マップ型

マップ型はある型(要素型)の要素の順序を持たない集合で、要素は別の型(キー型)のユニークなキーにより索引付けされます。マップ型の値はnilをとることがあります。

MapType     = "map" "[" KeyType "]" ElementType .
KeyType     = Type .

キーどうしの比較に使用するため、キー型においては比較演算子==!=比較演算子)が完全に実装されている必要があります。ゆえにキー型は論理値型、数値型、文字列型、ポインタ型、関数型、インタフェース型、マップ型、チャネル型である必要があります。キー型がインタフェース型のとき、比較演算子は動的なキー値を比較するために完全に定義されていなければなりません。比較できなければランタイムエラーとなります。

map [string] int
map [*T] struct { x, y float }
map [string] interface {}

要素の数は長さ(length)と呼ばれます。この値はマイナス値には成り得ません。マップmの長さは、組み込み関数len(m)を使用して調べることができ、コンパイル時に定数となります。マップ内の要素は、特殊な代入方法で実行中に追加、削除できます。

初期化されていないマップの値はnilです。空の新しいマップを作るには組み込み関数makeを使用します。make関数はパラメータにマップの型と、オプションでキャパシティのヒントをとります。

make(map[string] int)
make(map[string] int, 100)

キャパシティの初期値は指定したサイズを超えることはありません。マップは格納する項目が収まるようにサイズを広げます。

チャネル型

チャネルは同時に実行されるふたつの関数に、同期実行と特定の要素型の値を受け渡す通信機構を提供します。チャネル型の値はnilをとることがあります。

ChannelType   = Channel | SendChannel | RecvChannel .
Channel       = "chan" ElementType .
SendChannel   = "chan" "<-" ElementType .
RecvChannel   = "<-" "chan" ElementType .

作成と同時にチャネルは送受信できるようになります。変換または代入によってチャネルは送信のみ、または受信のみ行うよう強制することができます。この制約はチャネルの方向と呼ばれ、送信のみ、受信のみ、双方向(制約なし)のいずれかとなります。

chan T         // T型の値を送受信可能
chan<- float   // floatの送信のみ
<-chan int     // intの受信のみ

初期化されていないチャネルの値はnilです。初期化済みの新しいチャネルを作るには組み込み関数makeを使用します。make関数はパラメータにチャネルの型と、オプションでキャパシティをとります。

make(chan int, 100)

キャパシティは要素数であり、チャネルのバッファサイズを指定します。キャパシティがゼロより大きいとき、チャネルは非同期になり、バッファがいっぱいになるまで、送信はブロックすることなく成功します。キャパシティがゼロまたは指定しなかったときは、通信は送信・受信側双方が準備ができているときだけ成功します。

チャネルは組み込み関数closeとclosedを使ってクローズ、およびクローズされたか確認することができます。