型は、それらの値に特有の操作とメソッドとともに一連の値を決定します。型は、もしあれば型名で示すことができますが、その型がジェネリックである場合は型引数が続く必要があります。型は型リテラルを使用して指定することもでき、これは既存の型から型を構成します。

Type     = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit  = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
           SliceType | MapType | ChannelType .

言語は特定の型名を事前宣言しています。その他は型宣言または型パラメータリストで導入されます。複合型—配列、構造体、ポインタ、関数、インターフェース、スライス、マップ、およびチャネル型—は型リテラルを使用して構築できます。

事前宣言型、定義型、および型パラメータは名前付き型と呼ばれます。エイリアス宣言で与えられた型が名前付き型である場合、エイリアスは名前付き型を表します。

ブール型

ブール型は、事前宣言された定数truefalseで示されるブール真理値の集合を表します。事前宣言されたブール型はboolです。これは定義型です。

数値型

整数浮動小数点、または複素数型は、それぞれ整数、浮動小数点、または複素数の値の集合を表します。これらは集合的に数値型と呼ばれます。事前宣言されたアーキテクチャに依存しない数値型は次のとおりです:

uint8       すべての符号なし8ビット整数の集合(0〜255)
uint16      すべての符号なし16ビット整数の集合(0〜65535)
uint32      すべての符号なし32ビット整数の集合(0〜4294967295)
uint64      すべての符号なし64ビット整数の集合(0〜18446744073709551615)

int8        すべての符号付き8ビット整数の集合(-128〜127)
int16       すべての符号付き16ビット整数の集合(-32768〜32767)
int32       すべての符号付き32ビット整数の集合(-2147483648〜2147483647)
int64       すべての符号付き64ビット整数の集合(-9223372036854775808〜9223372036854775807)

float32     すべてのIEEE 754 32ビット浮動小数点数の集合
float64     すべてのIEEE 754 64ビット浮動小数点数の集合

complex64   float32の実部と虚部を持つすべての複素数の集合
complex128  float64の実部と虚部を持つすべての複素数の集合

byte        uint8のエイリアス
rune        int32のエイリアス

nビット整数の値はnビット幅で、2の補数演算を使用して表されます。

実装固有のサイズを持つ事前宣言された整数型のセットもあります:

uint     32ビットまたは64ビット
int      uintと同じサイズ
uintptr  ポインタ値の解釈されていないビットを格納するのに十分な大きさの符号なし整数

移植性の問題を避けるために、すべての数値型は定義型であり、したがってbyteuint8エイリアス)とruneint32のエイリアス)を除いて区別されます。異なる数値型が式または代入で混在している場合、明示的な変換が必要です。例えば、int32intは特定のアーキテクチャで同じサイズを持っていても同じ型ではありません。

文字列型

文字列型は文字列値の集合を表します。文字列値は(空の場合もある)バイトのシーケンスです。バイト数は文字列の長さと呼ばれ、決して負になりません。文字列は不変です:一度作成されると、文字列の内容を変更することは不可能です。事前宣言された文字列型はstringです。これは定義型です。

文字列sの長さは組み込み関数lenを使用して調べることができます。文字列が定数である場合、長さはコンパイル時定数です。文字列のバイトには整数インデックス0からlen(s)-1を使ってアクセスできます。そのような要素のアドレスを取ることは不正です。s[i]が文字列のi番目のバイトである場合、&s[i]は無効です。

配列型

配列は、要素型と呼ばれる単一の型の要素の番号付きシーケンスです。要素の数は配列の長さと呼ばれ、決して負になりません。

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

長さは配列の型の一部です。それはint型の値によって表現可能な負でない定数に評価される必要があります。配列aの長さは組み込み関数lenを使用して調べることができます。要素には整数インデックス0からlen(a)-1を使ってアドレスを指定できます。配列型は常に一次元ですが、多次元型を形成するために構成することができます。

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // [2]([2]([2]float64))と同じ

配列型Tは、T型の要素、またはTを構成要素として含む型(それらの含む型が配列または構造体型のみである場合)を直接的または間接的に持つことはできません。

// 無効な配列型
type (
	T1 [10]T1                 // T1の要素型はT1
	T2 [10]struct{ f T2 }     // T2は構造体の構成要素としてT2を含む
	T3 [10]T4                 // T3はT4の構造体の構成要素としてT3を含む
	T4 struct{ f T3 }         // T4は構造体内の配列T3の構成要素としてT4を含む
)

// 有効な配列型
type (
	T5 [10]*T5                // T5はポインタの構成要素としてT5を含む
	T6 [10]func() T6          // T6は関数型の構成要素としてT6を含む
	T7 [10]struct{ f []T7 }   // T7は構造体内のスライスの構成要素としてT7を含む
)

スライス型

スライスは基礎となる配列の連続したセグメントの記述子であり、その配列からの要素の番号付きシーケンスへのアクセスを提供します。スライス型は、その要素型の配列のすべてのスライスの集合を表します。要素の数はスライスの長さと呼ばれ、決して負になりません。初期化されていないスライスの値はnilです。

SliceType = "[" "]" ElementType .

スライスsの長さは組み込み関数lenで調べることができます。配列とは異なり、実行中に変化する可能性があります。要素には整数インデックス0からlen(s)-1を使ってアドレスを指定できます。与えられた要素のスライスインデックスは、基礎となる配列内の同じ要素のインデックスよりも小さい場合があります。

スライスは、一度初期化されると、常にその要素を保持する基礎となる配列に関連付けられます。したがって、スライスはその配列および同じ配列の他のスライスとストレージを共有します。対照的に、異なる配列は常に異なるストレージを表します。

スライスの基礎となる配列はスライスの終わりを超えて拡張することがあります。容量はその範囲の尺度です:それはスライスの長さとスライスを超えた配列の長さの合計です。その容量までの長さのスライスは、元のスライスから新しいものをスライシングすることによって作成できます。スライスaの容量は、組み込み関数cap(a)を使用して調べることができます。

指定された要素型Tに対する新しい初期化済みのスライス値は、組み込み関数makeを使用して作成できます。この関数はスライス型とパラメータとして長さ、およびオプションで容量を指定します。makeで作成されたスライスは、常に返されるスライス値が参照する新しい隠し配列を割り当てます。つまり、

make([]T, length, capacity)

を実行すると、配列を割り当ててスライシングするのと同じスライスが生成されるため、これらの2つの式は同等です:

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

配列と同様に、スライスは常に一次元ですが、より高次元のオブジェクトを構築するために構成することができます。配列の配列では、内部配列は構造上常に同じ長さです。しかし、スライスのスライス(またはスライスの配列)では、内部の長さは動的に変化する可能性があります。さらに、内部スライスは個別に初期化する必要があります。

構造体型

構造体は、フィールドと呼ばれる名前付き要素のシーケンスであり、各フィールドには名前と型があります。フィールド名は明示的に(IdentifierList)または暗黙的に(EmbeddedField)指定できます。構造体内では、空白でないフィールド名は一意でなければなりません。

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

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

型は指定されているが明示的なフィールド名のないフィールドは埋め込みフィールドと呼ばれます。埋め込みフィールドは、型名Tまたは非インターフェース型名へのポインタ*Tとして指定する必要があり、T自体はポインタ型または型パラメータであってはなりません。修飾されていない型名がフィールド名として機能します。

// 型T1、*T2、P.T3、*P.T4の4つの埋め込みフィールドを持つ構造体
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と競合
}

構造体xの埋め込みフィールドのフィールドまたはメソッドfは、x.fがそのフィールドまたはメソッドfを表す合法なセレクタである場合、昇格されたと呼ばれます。

昇格されたフィールドは構造体の通常のフィールドのように動作しますが、構造体の複合リテラル内でフィールド名として使用することはできません。

構造体型Sと型名Tが与えられた場合、昇格されたメソッドは次のように構造体のメソッドセットに含まれます:

  • Sが埋め込みフィールドTを含む場合、S*Sメソッドセットの両方に、レシーバTを持つ昇格されたメソッドが含まれます。*Sのメソッドセットには、レシーバ*Tを持つ昇格されたメソッドも含まれます。
  • Sが埋め込みフィールド*Tを含む場合、S*Sのメソッドセットの両方に、レシーバTまたは*Tを持つ昇格されたメソッドが含まれます。

フィールド宣言の後にはオプションの文字列リテラルタグが続くことがあり、これは対応するフィールド宣言内のすべてのフィールドの属性になります。空のタグ文字列は存在しないタグと同等です。タグはリフレクションインターフェースを通じて可視化され、構造体の型アイデンティティに参加しますが、それ以外は無視されます。

struct {
	x, y float64 ""  // 空のタグ文字列は存在しないタグと同様
	name string  "任意の文字列がタグとして許可される"
	_    [4]byte "ceci n'est pas un champ de structure"
}

// TimeStampプロトコルバッファに対応する構造体
// タグ文字列はプロトコルバッファのフィールド番号を定義します。
// これらはreflectパッケージで概説されている規約に従っています。
struct {
	microsec  uint64 `protobuf:"1"`
	serverIP6 uint64 `protobuf:"2"`
}

構造体型Tは、型Tのフィールド、または含む型が配列または構造体型のみである場合、直接的または間接的に構成要素としてTを含む型のフィールドを含むことはできません。

// 無効な構造体型
type (
	T1 struct{ T1 }            // T1はT1型のフィールドを含む
	T2 struct{ f [10]T2 }      // T2は配列の構成要素としてT2を含む
	T3 struct{ T4 }            // T3は構造体T4内の配列の構成要素としてT3を含む
	T4 struct{ f [10]T3 }      // T4は配列内の構造体T3の構成要素としてT4を含む
)

// 有効な構造体型
type (
	T5 struct{ f *T5 }         // T5はポインタの構成要素としてT5を含む
	T6 struct{ f func() T6 }   // T6は関数型の構成要素としてT6を含む
	T7 struct{ f [10][]T7 }    // T7は配列内のスライスの構成要素としてT7を含む
)

ポインタ型

ポインタ型は、特定の型(ポインタの基底型と呼ばれる)の変数へのすべてのポインタの集合を表します。初期化されていないポインタのnilです。

PointerType = "*" BaseType .
BaseType    = Type .
*Point
*[4]int

関数型

関数型は、同じパラメータと結果型を持つすべての関数の集合を表します。関数型の初期化されていない変数のnilです。

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

パラメータまたは結果のリスト内では、名前(IdentifierList)はすべて存在するかすべて存在しないかのいずれかでなければなりません。存在する場合、各名前は指定された型の1つの項目(パラメータまたは結果)を表し、シグネチャ内のすべての空白でない名前は一意でなければなりません。存在しない場合、各型はその型の1つの項目を表します。パラメータと結果のリストは常に括弧で囲まれますが、名前のない結果が正確に1つある場合は、括弧なしの型として記述できます。

関数シグネチャの最後の入力パラメータには、...を前置した型を持つことができます。このようなパラメータを持つ関数は可変引数と呼ばれ、そのパラメータに対してゼロ個以上の引数で呼び出すことができます。

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

インターフェース型

インターフェース型は型集合を定義します。インターフェース型の変数は、そのインターフェースの型集合に含まれる任意の型の値を格納できます。そのような型はそのインターフェースを実装すると言われます。インターフェース型の初期化されていない変数のnilです。

InterfaceType  = "interface" "{" { InterfaceElem ";" } "}" .
InterfaceElem  = MethodElem | TypeElem .
MethodElem     = MethodName Signature .
MethodName     = identifier .
TypeElem       = TypeTerm { "|" TypeTerm } .
TypeTerm       = Type | UnderlyingType .
UnderlyingType = "~" Type .

インターフェース型はインターフェース要素のリストによって指定されます。インターフェース要素はメソッドまたは型要素のいずれかであり、型要素は1つ以上の型項の和集合です。型項は単一の型または単一の基底型のいずれかです。

基本インターフェース

最も基本的な形式では、インターフェースは(空の場合もある)メソッドのリストを指定します。このようなインターフェースによって定義される型集合は、それらのメソッドをすべて実装する型の集合であり、対応するメソッドセットはインターフェースによって指定されたメソッドのみで構成されます。型集合がメソッドのリストだけで完全に定義できるインターフェースは、基本インターフェースと呼ばれます。

// シンプルなFileインターフェース
interface {
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Close() error
}

明示的に指定された各メソッドの名前は一意空白であってはなりません。

interface {
	String() string
	String() string  // 不正:Stringが一意でない
	_(x int)         // 不正:メソッドは空白でない名前を持つ必要がある
}

複数の型が同じインターフェースを実装することがあります。例えば、2つの型S1S2が以下のメソッドセットを持つ場合:

func (p T) Read(p []byte) (n int, err error)
func (p T) Write(p []byte) (n int, err error)
func (p T) Close() error

(ここでTS1またはS2のいずれかを表します)S1S2がどのような他のメソッドを持っていたり共有していたりしても、FileインターフェースはS1S2の両方によって実装されます。

インターフェースの型集合のメンバーであるすべての型は、そのインターフェースを実装します。任意の型は複数の異なるインターフェースを実装することがあります。例えば、すべての型はすべての(非インターフェース)型の集合を表す空インターフェースを実装します:

interface{}

便宜上、事前宣言された型anyは空インターフェースのエイリアスです [Go 1.18]。

同様に、次のインターフェース仕様を考えてみましょう。これはLockerというインターフェースを定義するために型宣言内に表示されます:

type Locker interface {
	Lock()
	Unlock()
}

S1S2も以下を実装している場合:

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

それらはFileインターフェースだけでなくLockerインターフェースも実装します。

埋め込みインターフェース

より一般的な形式では、インターフェースTはインターフェース要素として(修飾された場合もある)インターフェース型名Eを使用することがあります。これはTE埋め込むと呼ばれます [Go 1.14]。Tの型集合は、Tの明示的に宣言されたメソッドによって定義される型集合とTの埋め込まれたインターフェースの型集合の交差です。言い換えると、Tの型集合は、Tの明示的に宣言されたすべてのメソッドとEのすべてのメソッドを実装するすべての型の集合です [Go 1.18]。

type Reader interface {
	Read(p []byte) (n int, err error)
	Close() error
}

type Writer interface {
	Write(p []byte) (n int, err error)
	Close() error
}

// ReadWriterのメソッドは、Read、Write、Closeです。
type ReadWriter interface {
	Reader  // ReaderのメソッドをReadWriterのメソッドセットに含める
	Writer  // WriterのメソッドをReadWriterのメソッドセットに含める
}

インターフェースを埋め込む場合、同じ名前のメソッドは同一のシグネチャを持つ必要があります。

type ReadCloser interface {
	Reader   // ReaderのメソッドをReadCloserのメソッドセットに含める
	Close()  // 不正:Reader.CloseとCloseのシグネチャが異なる
}

一般的なインターフェース

最も一般的な形式では、インターフェース要素は任意の型項T、または基底型Tを指定する~T形式の項、またはt₁|t₂|…|tₙ形式の項の和集合であることもあります [Go 1.18]。メソッド仕様とともに、これらの要素はインターフェースの型集合を次のように正確に定義することを可能にします:

  • 空インターフェースの型集合は、すべての非インターフェース型の集合です。
  • 空でないインターフェースの型集合は、そのインターフェース要素の型集合の交差です。
  • メソッド仕様の型集合は、メソッドセットにそのメソッドを含むすべての非インターフェース型の集合です。
  • 非インターフェース型項の型集合は、その型だけからなる集合です。
  • ~T形式の項の型集合は、基底型がTであるすべての型の集合です。
  • 項の和集合t₁|t₂|…|tₙの型集合は、各項の型集合の和集合です。

「すべての非インターフェース型の集合」という量化は、プログラム内で宣言されたすべての(非インターフェース)型だけでなく、すべての可能なプログラム内のすべての可能な型を指し、したがって無限です。同様に、特定のメソッドを実装するすべての非インターフェース型の集合が与えられた場合、それらの型のメソッドセットの交差は、プログラム内のすべての型が常にそのメソッドを別のメソッドと対にしていても、ちょうどそのメソッドを含みます。

構成上、インターフェースの型集合はインターフェース型を含みません。

// int型のみを表すインターフェース
interface {
	int
}

// 基底型がintのすべての型を表すインターフェース
interface {
	~int
}

// 基底型がintでStringメソッドを実装するすべての型を表すインターフェース
interface {
	~int
	String() string
}

// 空の型集合を表すインターフェース:intかつstringである型は存在しない
interface {
	int
	string
}

~T形式の項では、Tの基底型はそれ自体でなければならず、Tはインターフェースであってはなりません。

type MyInt int

interface {
	~[]byte  // []byteの基底型はそれ自体
	~MyInt   // 不正:MyIntの基底型はMyIntではない
	~error   // 不正:errorはインターフェース
}

和集合要素は型集合の和集合を表します:

// Floatインターフェースはすべての浮動小数点型を表す
// (基底型がfloat32またはfloat64である名前付き型を含む)
type Float interface {
	~float32 | ~float64
}

Tまたは~T形式の項における型T型パラメータであってはならず、すべての非インターフェース項の型集合は互いに素でなければなりません(型集合の対ごとの交差は空でなければなりません)。型パラメータPが与えられた場合:

interface {
	P                // 不正:Pは型パラメータ
	int | ~P         // 不正:Pは型パラメータ
	~int | MyInt     // 不正:~intとMyIntの型集合は互いに素ではない(~intはMyIntを含む)
	float32 | Float  // 型集合が重複しているが、Floatはインターフェース
}

実装上の制限: 和集合(複数の項を持つ)は、事前宣言された識別子comparable、またはメソッドを指定するインターフェース、またはcomparableを埋め込むインターフェース、またはメソッドを指定するインターフェースを含むことはできません。

基本でないインターフェースは、型制約として、または制約として使用される他のインターフェースの要素としてのみ使用できます。それらは値や変数の型、または他の非インターフェース型の構成要素であることはできません。

var x Float                     // 不正:Floatは基本インターフェースではない

var x interface{} = Float(nil)  // 不正

type Floatish struct {
	f Float                 // 不正
}

インターフェース型Tは、直接的または間接的にTである、Tを含む、またはTを埋め込む型要素を埋め込むことはできません。

// 不正:BadはBad自身を埋め込むことはできない
type Bad interface {
	Bad
}

// 不正:Bad1はBad2を使って自身を埋め込むことはできない
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

// 不正:Bad3はBad3を含む和集合を埋め込むことはできない
type Bad3 interface {
	~int | ~string | Bad3
}

// 不正:Bad4は要素型としてBad4を持つ配列を埋め込むことはできない
type Bad4 interface {
	[10]Bad4
}

インターフェースの実装

TがインターフェースIを実装するのは、以下の場合です:

  • Tがインターフェースではなく、Iの型集合の要素である場合、または
  • Tがインターフェースであり、Tの型集合がIの型集合の部分集合である場合。

Tの値は、Tがインターフェースを実装する場合、そのインターフェースを実装します。

マップ型

マップは、要素型と呼ばれる1つの型の要素の順序付けられていないグループであり、キー型と呼ばれる別の型の一意のキーのセットによってインデックス付けされます。初期化されていないマップのnilです。

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

キー型のオペランドには比較演算子==!=が完全に定義されている必要があります。したがって、キー型は関数、マップ、またはスライスであってはなりません。キー型がインターフェース型である場合、これらの比較演算子は動的なキー値に対して定義されている必要があります。定義されていない場合は実行時パニックが発生します。

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

マップ要素の数はその長さと呼ばれます。マップmの場合、組み込み関数lenを使用して調べることができ、実行中に変化する可能性があります。要素は代入文を使用して実行中に追加でき、インデックス式で取得できます。組み込み関数deleteclearで削除できます。

新しい空のマップ値は、組み込み関数makeを使用して作成され、マップ型とオプションの容量ヒントを引数として取ります:

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

初期容量はそのサイズを制限しません:マップは、nilマップを除いて、格納される項目の数に対応するように成長します。nilマップは、要素を追加できないことを除いて、空のマップと同等です。

チャネル型

チャネルは、並行実行関数が指定された要素型の値を送信および受信することによって通信するためのメカニズムを提供します。初期化されていないチャネルのnilです。

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

オプションの<-演算子はチャネルの方向送信または受信)を指定します。方向が指定されている場合、チャネルは一方向であり、そうでない場合は双方向です。チャネルは代入または明示的な変換によって、送信のみまたは受信のみに制限できます。

chan T          // 型Tの値を送信および受信するために使用できる
chan<- float64  // float64を送信するためにのみ使用できる
<-chan int      // intを受信するためにのみ使用できる

<-演算子は可能な限り左端のchanに関連付けられます:

chan<- chan int    // chan<- (chan int)と同じ
chan<- <-chan int  // chan<- (<-chan int)と同じ
<-chan <-chan int  // <-chan (<-chan int)と同じ
chan (<-chan int)

新しい初期化されたチャネル値は、組み込み関数makeを使用して作成でき、チャネル型とオプションの容量を引数として取ります:

make(chan int, 100)

要素数での容量は、チャネル内のバッファのサイズを設定します。容量がゼロまたは存在しない場合、チャネルはバッファリングされず、送信者と受信者の両方が準備できている場合にのみ通信が成功します。それ以外の場合、チャネルはバッファリングされ、バッファが満杯でない(送信の場合)または空でない(受信の場合)場合、通信はブロックせずに成功します。nilチャネルは通信の準備ができていません。

チャネルは組み込み関数closeで閉じることができます。受信演算子の複数値の代入形式は、受信した値がチャネルが閉じられる前に送信されたかどうかを報告します。

単一のチャネルは、さらに同期せずに任意の数のゴルーチンによって、送信文受信操作、および組み込み関数caplenの呼び出しで使用できます。チャネルは先入れ先出しのキューとして機能します。たとえば、あるゴルーチンがチャネルに値を送信し、2番目のゴルーチンがそれらを受信する場合、値は送信された順序で受信されます。