Go/ブロック・宣言とスコープ

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go


ブロック[編集]

ブロック(Blocks)は、一致する波括弧の中に宣言とステートメントを並べた、空の可能性のあるシーケンスです[1]

構文
Block = "{" StatementList "}" ;
StatementList = { Statement ";" } ;

ソースコードには、明示的なブロックの他に、暗黙のブロックがあります。

  1. すべてのGoのソーステキストを包含するユニバーサルブロック(universe block)があります。
  2. 各パッケージには、そのパッケージの全てのGoソースを含むpackage blockがあります。
  3. 各ファイルには、そのファイルに含まれる全てのGoのソースを含むfile blockがあります。
  4. 各 "if"、"for"、"switch" 文は、それぞれの暗黙のブロックとみなされます。
  5. "switch "または "select" 文の各節は、暗黙のブロックとして機能します。

ブロックはスコーピングに影響を与えます。

宣言とスコープ[編集]

宣言とスコープ(Declarations and scope)[2]

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

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

構文
Declaration   = ConstDecl | TypeDecl | VarDecl ;
TopLevelDecl  = Declaration | FunctionDecl | MethodDecl ;

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

Goはブロックを使って語彙的にスコープを設定します。

  1. 宣言済みの識別子のスコープはユニバーサルブロックです。
  2. 定数、型、変数、トップレベルで宣言された関数(メソッドは除く)を示す識別子のスコープは、パッケージブロックです。
  3. インポートされたパッケージのパッケージ名のスコープは、インポート宣言のあるファイルのファイルブロックとなります。
  4. メソッドのレシーバー、関数の仮引数、結果変数を示す識別子のスコープは、関数本体となります。
  5. 関数内で宣言された定数や変数の識別子のスコープは、ConstSpecまたはVarSpec(短い変数宣言の場合はShortVarDecl)の末尾から、最も内側の包含ブロックの末尾までとなります。
  6. 関数内で宣言された型識別子のスコープは、TypeSpec内の識別子から始まり、最も内側の包含ブロックの終わりで終わります。

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

package節は宣言ではなく、パッケージ名はどのスコープにも表示されない。その目的は、同じパッケージに属するファイルを識別することと、インポート宣言のデフォルトのパッケージ名を指定することです。

ラベルスコープ[編集]

ラベルはラベル付きステートメントで宣言され、break, continue, goto 文で使用されます[3]。 一度も使用されないラベルを定義することは違法です。 他の識別子とは異なり、ラベルはブロックスコープを持たず、ラベルではない識別子とは衝突しません。 ラベルのスコープは、ラベルが宣言されている関数のボディであり、ネストされた関数のボディは含まれません。

ラベル付きステートメント[編集]

ラベル付けされたステートメントは、goto, break, continue 文のターゲットになることがあります[4]

構文
LabeledStmt = Label ":" Statement ;
Label       = identifier ;
Error: log.Panic("error encountered")

ブランク識別子[編集]

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

宣言済み識別子[編集]

ユニバースブロックでは、いくつかの識別子が暗黙的に宣言されています[6]

エクスポートされた識別子[編集]

識別子は、他のパッケージからのアクセスを可能にするためにエクスポートすることができます。識別子は以下の場合にエクスポートされます[7]

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

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

識別子の一意性[編集]

識別子の集合が与えられたとき、識別子が集合内の他のすべてのものと異なる場合、その識別子は一意と呼ばれます[8]。 2つの識別子が異なる場合とは、スペルが異なる場合や、異なるパッケージに含まれていてエクスポートされない場合などです。 それ以外の場合は、同じです。

定数宣言[編集]

定数宣言は、識別子のリスト(定数の名前)を定数式のリストの値に束縛します[9]。 識別子の数は式の数と等しくなければならず、左からn番目の識別子は右からn番目の式の値に束縛されます。

iota[編集]

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

型宣言[編集]

型宣言は、識別子である型名を型に結びつけるものです。型宣言には、エイリアス宣言と型定義の2つの形式があります。[11]

構文
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) ;
TypeSpec = AliasDecl | TypeDef ;
エイリアス宣言
エイリアス宣言は、識別子を与えられた型に結びつけるものです。
構文
AliasDecl = identifier "=" Type ;

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

型定義
型定義は、与えられた型と同じ基本的な型と操作を持つ新しい別個の型を作成し、その型に識別子を結合します。
構文
TypeDef = identifier Type ;

新しい型は、定義された型と呼ばれます。この型は、作成された型を含め、他の型とは異なります。

変数宣言[編集]

変数宣言では、1つまたは複数の変数を作成し、対応する識別子を結合し、それぞれに型と初期値を与えます[12]

短い変数宣言[編集]

短い変数宣言
短い変数宣言は、以下の構文を使用します[13]
構文
ShortVarDecl = IdentifierList ":=" ExpressionList ;
この構文は、初期化式を持つが型を持たない通常の変数宣言の短縮形です。
この構文は、ユニバースブロック (universe block) の変数には適用できません。
この構文は、if文, for文, switch文の初期化句(オプショナル)に使えます。
多値の短い変数宣言
(var と違って)同じ変数名を再宣言できる。

関数宣言[編集]

関数宣言(Function declarations)は、識別子である関数名を関数に結びつけるものです[14]

構文
FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] ;
FunctionName = identifier ;
FunctionBody = Block ;
Go1.18から、関数名と仮引数リストの前の丸括弧の間に鉤括弧でくくられた型パラメータを指定することが出来るようになりました。

関数のシグネチャが結果仮引数を宣言している場合、関数本体のステートメントリストは終了ステートメントで終わらなければなりません。

func IndexRune(s string, r rune) int {
	for i, c := range s {
		if c == r {
			return i
		}
	}
	// invalid: missing return statement
}

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

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

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

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

メソッド宣言[編集]

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

構文
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] ;
Receiver   = Parameters ;

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

Go1.18から、レシーバーも型パラメータを伴うことが出来る様になりました。

空白ではないレシーバーの識別子は、メソッドの署名(Signature)の中で一意でなければなりません。レシーバーの値がメソッドの内部で参照されない場合、その識別子は宣言で省略することができます。これは、関数やメソッドの仮引数についても同様です。

基底型の場合、それに束縛されるメソッドの空白でない名前は一意でなければならない。基本型が構造体の場合、空白でないメソッド名とフィールド名は一意でなければなりません。

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であるメソッドLengthとScaleを基本型Pointにバインドします。

レシーバーの基本型がジェネリック型の場合、レシーバー仕様(receiver specification)はメソッドが使用する対応する型パラメータを宣言する必要があります。 これにより、レシーバー型パラメータはメソッドから利用できるようになります。 型パラメタの宣言は、文法的にはレシーバー基本型のインスタンス化のようなもので、型引数は宣言された型パラメタを示す識別子で、レシーバー基本型の各型パラメタに対して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 に対応する

脚註[編集]

  1. ^ “Blocks ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Blocks. 
  2. ^ “Declarations and scope¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Declarations_and_scope. 
  3. ^ “Label scopes¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Label_scopes. 
  4. ^ “Labeled statements¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Labeled_statements. 
  5. ^ “Blank identifier¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Blank_identifier. 
  6. ^ “Predeclared identifiers¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Predeclared_identifiers. 
  7. ^ “Exported identifiers¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Exported_identifiers. 
  8. ^ “Uniqueness of identifiers¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Uniqueness_of_identifiers. 
  9. ^ “Constant declarations¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Constant_declarations. 
  10. ^ “Iota¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Iota. 
  11. ^ “Type declarations ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Type_declarations. 
  12. ^ “Variable declarations ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Variable_declarations. 
  13. ^ “Short variable declarations ¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Short_variable_declarations. 
  14. ^ “Function declarations¶”. The Go Programming Language Specification. The Go website. (March 10, 2022). https://golang.org/ref/spec#Function_declarations. 
  15. ^ “Method declarations¶”. The Go Programming Language Specification. The Go website. (March 10, 2022). https://golang.org/ref/spec#Method_declarations.