宣言とスコープ

宣言は、空白でない識別子を定数型パラメータ変数関数ラベル、またはパッケージに結び付けます。プログラム内のすべての識別子は宣言されなければなりません。同じブロック内で識別子を2回宣言することはできず、ファイルブロックとパッケージブロックの両方で識別子を宣言することもできません。

空白識別子は、宣言内で他の識別子のように使用できますが、バインディングを導入しないため宣言されません。パッケージブロックでは、識別子initinit関数宣言にのみ使用でき、空白識別子と同様に新しいバインディングを導入しません。

Declaration  = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .

宣言された識別子のスコープは、その識別子が指定された定数、型、変数、関数、ラベル、またはパッケージを表すソーステキストの範囲です。

Goはブロックを使用する字句スコープを持ちます:

  1. 定義済み識別子のスコープはユニバースブロックです。

  2. トップレベル(関数外)で宣言された定数、型、変数、関数(メソッドは除く)を表す識別子のスコープはパッケージブロックです。

  3. インポートされたパッケージのパッケージ名のスコープは、インポート宣言を含むファイルのファイルブロックです。

  4. メソッドレシーバ、関数パラメータ、または結果変数を表す識別子のスコープは関数本体です。

  5. 関数の型パラメータまたはメソッドレシーバによって宣言された識別子のスコープは、関数の名前の後から始まり、関数本体の終わりで終わります。

  6. 型の型パラメータを表す識別子のスコープは、型の名前の後から始まり、TypeSpecの終わりで終わります。

  7. 関数内で宣言された定数または変数識別子のスコープは、ConstSpecまたはVarSpec(短い変数宣言の場合はShortVarDecl)の終わりから始まり、最も内側の包含ブロックの終わりで終わります。

  8. 関数内で宣言された型識別子のスコープは、TypeSpecの識別子から始まり、最も内側の包含ブロックの終わりで終わります。

ブロック内で宣言された識別子は、内部ブロックで再宣言できます。内部宣言の識別子がスコープ内にある間、それは内部宣言によって宣言されたエンティティを表します。

パッケージ句は宣言ではありません。パッケージ名はどのスコープにも現れません。その目的は、同じパッケージに属するファイルを識別し、インポート宣言のデフォルトパッケージ名を指定することです。

ラベルスコープ

ラベルはラベル付きステートメントによって宣言され、“break”“continue”“goto”ステートメントで使用されます。使用されないラベルを定義することは違法です。他の識別子とは異なり、ラベルはブロックスコープではなく、ラベルではない識別子と競合しません。ラベルのスコープは、それが宣言されている関数の本体であり、ネストされた関数の本体は除外されます。

空白識別子

空白識別子はアンダースコア文字_で表されます。通常の(空白でない)識別子の代わりに匿名のプレースホルダとして機能し、宣言オペランド代入文で特別な意味を持ちます。

定義済み識別子

以下の識別子はユニバースブロックで暗黙的に宣言されています [Go 1.18] [Go 1.21]:

型:
	any bool byte comparable
	complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr

定数:
	true false iota

ゼロ値:
	nil

関数:
	append cap clear close complex copy delete imag len
	make max min new panic print println real recover

エクスポートされた識別子

識別子は、別のパッケージからアクセスできるようにエクスポートされることがあります。以下の両方の条件を満たす場合、識別子はエクスポートされます:

  1. 識別子の名前の最初の文字がUnicodeの大文字(Unicodeの文字カテゴリLu)である。
  2. 識別子がパッケージブロックで宣言されているか、フィールド名またはメソッド名である。

それ以外のすべての識別子はエクスポートされません。

識別子の一意性

識別子のセットが与えられた場合、そのセット内の他のすべての識別子と異なる場合、識別子は一意と呼ばれます。2つの識別子は、異なるスペルを持つか、異なるパッケージに現れ、エクスポートされていない場合に異なります。それ以外の場合は同じです。

定数宣言

定数宣言は、識別子のリスト(定数の名前)を定数式のリストの値に結び付けます。識別子の数は式の数と等しくなければならず、左側のn番目の識別子は右側のn番目の式の値に結び付けられます。

ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .

IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .

型が存在する場合、すべての定数は指定された型を取り、式はその型に代入可能でなければならず、型パラメータであってはなりません。型が省略された場合、定数は対応する式の個々の型を取ります。式の値が型付けされていない定数の場合、宣言された定数は型付けされないままで、定数識別子は定数値を表します。例えば、式が浮動小数点リテラルの場合、小数部がゼロでも、定数識別子は浮動小数点定数を表します。

const Pi float64 = 3.14159265358979323846
const zero = 0.0         // 型付けされていない浮動小数点定数
const (
	size int64 = 1024
	eof        = -1  // 型付けされていない整数定数
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", 型付けされていない整数と文字列定数
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

括弧付きのconst宣言リスト内では、最初のConstSpec以外の式リストを省略することができます。そのような空のリストは、型がある場合は、最初の先行する非空の式リストとその型のテキスト置換と同等です。したがって、式のリストを省略することは、前のリストを繰り返すことと同等です。識別子の数は、前のリストの式の数と等しくなければなりません。iota定数ジェネレータと共に、このメカニズムは連続する値の軽量宣言を可能にします:

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // この定数はエクスポートされません
)

Iota

定数宣言内で、定義済み識別子iotaは連続する型付けされていない整数定数を表します。その値は、ゼロから始まる、その定数宣言内の対応するConstSpecのインデックスです。これを使用して関連する定数のセットを構築できます:

const (
	c0 = iota  // c0 == 0
	c1 = iota  // c1 == 1
	c2 = iota  // c2 == 2
)

const (
	a = 1 << iota  // a == 1  (iota == 0)
	b = 1 << iota  // b == 2  (iota == 1)
	c = 3          // c == 3  (iota == 2, 未使用)
	d = 1 << iota  // d == 8  (iota == 3)
)

const (
	u         = iota * 42  // u == 0     (型付けされていない整数定数)
	v float64 = iota * 42  // v == 42.0  (float64定数)
	w         = iota * 42  // w == 84    (型付けされていない整数定数)
)

const x = iota  // x == 0
const y = iota  // y == 0

定義により、同じConstSpec内でのiotaの複数の使用はすべて同じ値を持ちます:

const (
	bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
	bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
	_, _                                  //                        (iota == 2, 未使用)
	bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
)

この最後の例は、最後の非空の式リストの暗黙的な繰り返しを利用しています。

型宣言

型宣言は識別子(型名)をに結び付けます。型宣言には2つの形式があります:エイリアス宣言と型定義です。

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

エイリアス宣言

エイリアス宣言は識別子を指定された型に結び付けます [Go 1.9]。

AliasDecl = identifier [ TypeParameters ] "=" Type .

識別子のスコープ内では、それは指定された型のエイリアスとして機能します。

type (
	nodeList = []*Node  // nodeListと[]*Nodeは同一の型です
	Polar    = polar    // PolarとpolarはPolarは同じ型を表します
)

エイリアス宣言が型パラメータを指定する場合 [Go 1.24]、型名はジェネリックエイリアスを表します。ジェネリックエイリアスは使用時にインスタンス化されなければなりません。

type set[P comparable] = map[P]bool

エイリアス宣言では、指定された型は型パラメータであってはなりません。

type A[P any] = P    // 不正: Pは型パラメータです

型定義

型定義は、指定された型と同じ基底型および操作を持つ新しい異なる型を作成し、識別子(型名)をそれに結び付けます。

TypeDef = identifier [ TypeParameters ] Type .

新しい型は定義された型と呼ばれます。それは、作成元の型を含む他のどの型とも異なります

type (
	Point struct{ x, y float64 }  // PointとPとstruct{ x, y float64 }は異なる型です
	polar Point                   // polarとPointは異なる型を表します
)

type TreeNode struct {
	left, right *TreeNode
	value any
}

type Block interface {
	BlockSize() int
	Encrypt(src, dst []byte)
	Decrypt(src, dst []byte)
}

定義された型にはメソッドを関連付けることができます。指定された型に結び付けられたメソッドは継承しませんが、インタフェース型または複合型の要素のメソッドセットは変更されません:

// Mutexは2つのメソッド、LockとUnlockを持つデータ型です。
type Mutex struct         { /* Mutexフィールド */ }
func (m *Mutex) Lock()    { /* Lockの実装 */ }
func (m *Mutex) Unlock()  { /* Unlockの実装 */ }

// NewMutexはMutexと同じ構成を持ちますが、そのメソッドセットは空です。
type NewMutex Mutex

// PtrMutexの基底型*Mutexのメソッドセットは変更されませんが、
// PtrMutexのメソッドセットは空です。
type PtrMutex *Mutex

// *PrintableMutexのメソッドセットには、埋め込みフィールドMutexに結び付けられた
// メソッドLockとUnlockが含まれます。
type PrintableMutex struct {
	Mutex
}

// MyBlockは、Blockと同じメソッドセットを持つインタフェース型です。
type MyBlock Block

型定義は、異なるブール型、数値型、または文字列型を定義し、それらにメソッドを関連付けるために使用できます:

type TimeZone int

const (
	EST TimeZone = -(5 + iota)
	CST
	MST
	PST
)

func (tz TimeZone) String() string {
	return fmt.Sprintf("GMT%+dh", tz)
}

型定義が型パラメータを指定する場合、型名はジェネリック型を表します。ジェネリック型は使用時にインスタンス化されなければなりません。

type List[T any] struct {
	next  *List[T]
	value T
}

型定義では、指定された型は型パラメータであってはなりません。

type T[P any] P    // 不正: Pは型パラメータです

func f[T any]() {
	type L T   // 不正: Tは囲む関数によって宣言された型パラメータです
}

ジェネリック型にもメソッドを関連付けることができます。この場合、メソッドレシーバーはジェネリック型定義に存在するのと同じ数の型パラメータを宣言する必要があります。

// メソッドLenは連結リストlの要素数を返します。
func (l *List[T]) Len() int  { … }

型パラメータ宣言

型パラメータリストは、ジェネリック関数または型宣言の型パラメータを宣言します。型パラメータリストは通常の関数パラメータリストのように見えますが、型パラメータ名はすべて存在する必要があり、リストは括弧ではなく角括弧で囲まれています [Go 1.18]。

TypeParameters = "[" TypeParamList [ "," ] "]" .
TypeParamList  = TypeParamDecl { "," TypeParamDecl } .
TypeParamDecl  = IdentifierList TypeConstraint .

リスト内のすべての非空の名前は一意でなければなりません。各名前は型パラメータを宣言し、これは宣言内で(まだ)未知の型のプレースホルダとして機能する新しい異なる名前付き型です。型パラメータは、ジェネリック関数または型のインスタンス化時に型引数に置き換えられます。

[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]

通常の関数パラメータがパラメータ型を持つのと同様に、各型パラメータには型制約と呼ばれる対応する(メタ)型があります。

ジェネリック型の型パラメータリストが、テキストP Cが有効な式を形成するような制約Cを持つ単一の型パラメータPを宣言する場合、構文解析の曖昧さが生じます:

type T[P *C] …
type T[P (C)] …
type T[P *C|Q] …
…

これらのまれなケースでは、型パラメータリストは式と区別できず、型宣言は配列型宣言として解析されます。この曖昧さを解決するには、制約をインタフェースに埋め込むか、末尾のカンマを使用します:

type T[P interface{*C}] …
type T[P *C,] …

型パラメータは、ジェネリック型に関連付けられたメソッド宣言のレシーバー指定によっても宣言できます。

ジェネリック型Tの型パラメータリスト内では、型制約は(直接的に、または別のジェネリック型の型パラメータリストを通じて間接的に)Tを参照することはできません。

type T1[P T1[P]] …                    // 不正: T1は自身を参照します
type T2[P interface{ T2[int] }] …     // 不正: T2は自身を参照します
type T3[P interface{ m(T3[int])}] …   // 不正: T3は自身を参照します
type T4[P T5[P]] …                    // 不正: T4はT5を参照し、
type T5[P T4[P]] …                    //        T5はT4を参照します

type T6[P int] struct{ f *T6[P] }     // 正常: T6への参照は型パラメータリスト内にありません

型制約

型制約は、それぞれの型パラメータに許容される型引数のセットを定義し、その型パラメータの値でサポートされる操作を制御するインタフェースです [Go 1.18]。

TypeConstraint = TypeElem .

制約がinterface{E}の形式のインタフェースリテラルで、Eは(メソッドではなく)埋め込まれた型要素である場合、型パラメータリストでは便宜上、囲むinterface{ … }を省略できます:

[T []P]                      // = [T interface{[]P}]
[T ~int]                     // = [T interface{~int}]
[T int|string]               // = [T interface{int|string}]
type Constraint ~int         // 不正: ~intは型パラメータリスト内にありません

定義済みインタフェース型 comparableは、厳密に比較可能なすべての非インタフェース型のセットを表します [Go 1.18]。

型パラメータではないインタフェースは比較可能ですが、厳密に比較可能ではないため、comparableを実装しません。ただし、それらはcomparable満たします

int                          // comparableを実装します(intは厳密に比較可能です)
[]byte                       // comparableを実装しません(スライスは比較できません)
interface{}                  // comparableを実装しません(上記参照)
interface{ ~int | ~string }  // 型パラメータのみ: comparableを実装します(int、string型は厳密に比較可能です)
interface{ comparable }      // 型パラメータのみ: comparableを実装します(comparableは自身を実装します)
interface{ ~int | ~[]byte }  // 型パラメータのみ: comparableを実装しません(スライスは比較できません)
interface{ ~struct{ any } }  // 型パラメータのみ: comparableを実装しません(フィールドanyは厳密に比較可能ではありません)

comparableインタフェースおよびcomparableを(直接的または間接的に)埋め込むインタフェースは、型制約としてのみ使用できます。それらは値または変数の型、または他の非インタフェース型のコンポーネントになることはできません。

型制約の満足

型引数Tは、TCによって定義された型セットの要素である場合、つまりTC実装する場合、型制約C満足します。例外として、厳密に比較可能な型制約は、比較可能な(必ずしも厳密に比較可能ではない)型引数によって満たされることもあります [Go 1.20]。より正確には:

型Tは、次の場合に制約C満足します

型引数        型制約                      // 制約満足

int          interface{ ~int }          // 満足: intはinterface{ ~int }を実装します
string       comparable                 // 満足: stringはcomparableを実装します(stringは厳密に比較可能です)
[]byte       comparable                 // 満足しない: スライスは比較できません
any          interface{ comparable; int } // 満足しない: anyはinterface{ int }を実装しません
any          comparable                 // 満足: anyは比較可能で基本インタフェースanyを実装します
struct{f any} comparable                // 満足: struct{f any}は比較可能で基本インタフェースanyを実装します
any          interface{ comparable; m() } // 満足しない: anyは基本インタフェースinterface{ m() }を実装しません
interface{ m() } interface{ comparable; m() } // 満足: interface{ m() }は比較可能で基本インタフェースinterface{ m() }を実装します

制約満足ルールの例外により、型パラメータ型のオペランドの比較は実行時にパニックを引き起こす可能性があります(比較可能な型パラメータは常に厳密に比較可能であるにもかかわらず)。

変数宣言

変数宣言は1つまたは複数の変数を作成し、対応する識別子をそれらに結び付け、それぞれに型と初期値を与えます。

VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
	i       int
	u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]  // マップ検索; "found"にのみ興味がある

式のリストが与えられた場合、変数は代入文のルールに従って式で初期化されます。そうでない場合、各変数はそのゼロ値に初期化されます。

型が存在する場合、各変数にはその型が与えられます。そうでない場合、各変数は代入における対応する初期化値の型が与えられます。その値が型付けされていない定数である場合、まず暗黙的にそのデフォルト型変換されます。型付けされていないブール値の場合、まず暗黙的にbool型に変換されます。定義済み識別子nilは、明示的な型のない変数を初期化するために使用することはできません。

var d = math.Sin(0.5)  // dはfloat64です
var i = 42             // iはintです
var t, ok = x.(T)      // tはT、okはboolです
var n = nil            // 不正

実装上の制限:コンパイラは、変数が使用されない場合、関数本体内での変数宣言を不正にすることがあります。

短い変数宣言

短い変数宣言は以下の構文を使用します:

ShortVarDecl = IdentifierList ":=" ExpressionList .

これは、初期化式はあるが型のない通常の変数宣言の省略形です:

"var" IdentifierList "=" ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe()  // os.Pipe()は接続されたFileのペアとエラー(あれば)を返します
_, y, _ := coord(p)   // coord()は3つの値を返します; y座標にのみ興味があります

通常の変数宣言とは異なり、短い変数宣言は、元々同じブロック内(またはブロックが関数本体の場合はパラメータリスト)で同じ型で宣言されており、少なくとも1つの非空白変数が新しい場合に変数を再宣言することができます。結果として、再宣言は複数変数の短い宣言でのみ表示できます。再宣言は新しい変数を導入せず、単に元の変数に新しい値を割り当てるだけです。:=の左側の非空白変数名は一意でなければなりません。

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // offsetを再宣言します
x, y, x := 1, 2, 3                        // 不正: :=の左側でxが繰り返されています

短い変数宣言は関数内でのみ表示できます。“if”“for”、または“switch”文のイニシャライザなど、一部のコンテキストでは、ローカルな一時変数を宣言するために使用できます。

関数宣言

関数宣言は識別子(関数名)を関数に結び付けます。

FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .

関数のシグネチャが結果パラメータを宣言する場合、関数本体のステートメントリストは終了ステートメントで終わる必要があります。

func IndexRune(s string, r rune) int {
	for i, c := range s {
		if c == r {
			return i
		}
	}
	// 不正: return文が欠けています
}

関数宣言が型パラメータを指定する場合、関数名はジェネリック関数を表します。ジェネリック関数は、呼び出されたり値として使用されたりする前にインスタンス化されなければなりません。

func min[T ~int|~float64](x, y T) T {
	if x < y {
		return x
	}
	return y
}

型パラメータのない関数宣言は本体を省略することができます。このような宣言は、アセンブリルーチンなど、Go外で実装される関数のシグネチャを提供します。

func flushICache(begin, end uintptr)  // 外部で実装

メソッド宣言

メソッドはレシーバーを持つ関数です。メソッド宣言は識別子(メソッド名)をメソッドに結び付け、メソッドをレシーバーの基底型に関連付けます。

MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver   = Parameters .

レシーバーはメソッド名の前に追加のパラメーターセクションを介して指定されます。そのパラメーターセクションは単一の非可変パラメーター、つまりレシーバーを宣言する必要があります。その型は定義されたTまたは定義された型Tへのポインタでなければならず、場合によっては角括弧で囲まれた型パラメーター名[P1, P2, …]のリストが続きます。Tはレシーバーの基底型と呼ばれます。レシーバー基底型はポインタまたはインターフェース型であってはならず、メソッドと同じパッケージで定義されている必要があります。メソッドはそのレシーバー基底型にバインドされていると言われ、メソッド名は型Tまたは*Tセレクタ内でのみ表示されます。

空白レシーバー識別子はメソッドシグネチャ内で一意でなければなりません。メソッドの本体内でレシーバーの値が参照されない場合、その識別子は宣言で省略することができます。同じことが一般に関数とメソッドのパラメーターにも適用されます。

基底型については、それにバインドされたメソッドの非空白名は一意でなければなりません。基底型が構造体型の場合、非空白メソッド名とフィールド名は異なっていなければなりません。

定義された型Pointが与えられた場合、以下の宣言:

func (p *Point) Length() float64 {
	return math.Sqrt(p.x * p.x + p.y * p.y)
}

func (p *Point) Scale(factor float64) {
	p.x *= factor
	p.y *= factor
}

は、レシーバー型*Pointを持つメソッドLengthScaleを基底型Pointにバインドします。

レシーバー基底型がジェネリック型の場合、レシーバー指定はメソッドが使用するための対応する型パラメーターを宣言する必要があります。これにより、レシーバー型パラメーターがメソッドで利用可能になります。構文的には、この型パラメーター宣言はレシーバー基底型のインスタンス化のように見えます:型引数は宣言される型パラメーターを表す識別子でなければならず、レシーバー基底型の各型パラメーターに対して1つずつです。型パラメーター名はレシーバー基底型定義の対応するパラメーター名と一致する必要はなく、すべての非空白パラメーター名はレシーバーパラメーターセクションとメソッドシグネチャで一意でなければなりません。レシーバー型パラメーター制約はレシーバー基底型定義によって暗示されます:対応する型パラメーターには対応する制約があります。

type Pair[A, B any] struct {
	a A
	b B
}

func (p Pair[A, B]) Swap() Pair[B, A]  { … }  // レシーバーはA, Bを宣言します
func (p Pair[First, _]) First() First  { … }  // レシーバーはFirstを宣言し、PairのAに対応します

レシーバー型が(ポインタを介して)エイリアスで表される場合、エイリアスはジェネリックであってはならず、インスタンス化されたジェネリック型を表してはなりません。これは、別のエイリアスを介して直接的にも間接的にも、ポインタの間接参照に関係なく適用されます。

type GPoint[P any] = Point
type HPoint        = *GPoint[int]
type IPair         = Pair[int, int]

func (*GPoint[P]) Draw(P)   { … }  // 不正: エイリアスはジェネリックであってはなりません
func (HPoint) Draw(P)       { … }  // 不正: エイリアスはインスタンス化された型GPoint[int]を表してはなりません
func (*IPair) Second() int  { … }  // 不正: エイリアスはインスタンス化された型Pair[int, int]を表してはなりません