The Go Programming Language Specificationの翻訳、8回目です。
前回までの訳はGo言語仕様[日本語訳]にまとめてあります。
宣言とスコープ
宣言をおこなうことによって識別子(ブランク識別子を除く)は、定数、型、変数、関数、パッケージにバインドされます。プログラム内のすべての識別子は、宣言されなければなりません。識別子は同じブロック内で2回宣言されることはありません、また識別子をファイルブロックとパッケージブロックの両方で宣言することはできません。
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
スコープとは、ソース内で宣言済み識別子に割り当てられた定数、型、変数、関数、パッケージを参照可能な範囲のことです。
Go言語ではブロックを使ってスコープ制御を行います。
- 事前宣言済み識別子が持つスコープは、ユニバースブロックです。
- トップレベル(関数の外)にて宣言されている定数、型、変数、関数の識別子が持つスコープは、パッケージブロックです。
- インポートされたパッケージの識別子が持つスコープは、そのimport宣言を含んでいるファイルブロックです。
- 関数のパラメータまたは戻り値として宣言されている変数の識別子が持つスコープは、関数の本体部分です。
- 関数のなかで宣言されている定数または変数の識別子が持つスコープは、ConstSpecまたはVarSpecの直後から始まり、その識別子を含んでいるブロックの終わりまでです。
- 関数内で宣言した型の識別子が持つスコープは、TypeSpecの識別子から始まり、その識別子を含んでいるブロックの終わりまでです。
ブロック内で宣言された識別子は、内側のブロック内で再宣言されます。ブロック内で宣言した識別子がスコープ内にある間、その識別子は宣言したときの実体を表しつづけます。
パッケージ節は宣言ではありません。パッケージ名がどのスコープにも現れることがないからです。パッケージ節は同一のパッケージに所属するファイルを識別するためと、インポートされるときにデフォルトとして使われるパッケージ名を定義するためのものです。
ラベルのスコープ
ラベルはラベルステートメントによって宣言され、break
、continue
、goto
ステートメント内で使用されます(§Break ステートメント、§Continue ステートメント、§Goto ステートメント)。ラベルはその他の識別子と異なり、ブロックによるスコープ制御は行われず、ラベル以外の識別子との重複が許されます。ラベルのスコープは、そのラベルが宣言されている関数の本体内ですが、ネストした関数はスコープ外です。
事前宣言済み識別子
下に示す識別子は、ユニバースブロック内で明示的に宣言されています。
基本型: bool byte float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 アーキテクチャ仕様に従う型: float int uint uintptr 定数: true false iota ゼロ値: nil 関数: cap close closed len make new panic panicln print println
エクスポートされた識別子
識別子は、他パッケージからの限定付き識別子を使ったアクセスを許可するためにエクポートされることがあります。識別子は下の2つの条件を満たすときエクスポートされます。
- 識別子の名前の最初の一文字が大文字(ユニコードクラス”Lu”)である。なおかつ
- 識別子がパッケージブロック内で宣言されている、またはパッケージブロック内で宣言された型のフィールドかメソッドである。
それ以外の識別子がエクスポートされることはありません。
ブランク識別子
アンダースコア(_)を使って記述されるブランク識別子は、他の識別子と同じく宣言にて使用されますが、その宣言でバインドは行われません。
定数の宣言
定数の宣言では、識別子(定数名)のカンマ区切りのリストを、定数式のリストにバインドします。識別子と式の数は一致している必要があり、左辺のn番目の識別子が、右辺のn番目の式にバインドされます。
ConstDecl = "const" ( ConstSpec | "(" [ ConstSpecList ] ")" ) . ConstSpecList = ConstSpec { ";" ConstSpec } [ ";" ] . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = identifier { "," identifier } . ExpressionList = Expression { "," Expression } .
定数の宣言時に型を指定したときは、定数はその型になり、式はその型に対して代入の適合性を持たなくてはならなくなります。型を省略したときは、型と対応する式によってそれぞれの型が決まります。式の値が型を持たない定数のときは、宣言された定数の識別子も同様に型を持たない定数値を表します。たとえば式が浮動小数点リテラルのとき、たとえ小数部の値が0であっても、識別子は浮動小数点の定数を表します。
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 float = 0, 3 // u = 0.0, v = 3.0
括弧でくくられたconst
宣言リスト内では、式リストは最初の宣言以外では省略可能です。このように式リストが省略されたときは、直近で使用した式リスト(型が指定されていれば型も)が適用されます。したがって、式リストを省略することは前の式リストを繰り返すことと同じになります。識別子の数は、直前の式リスト内の式の数と一致していなければなりません。iota定数ジェネレータを使うことで、シーケンシャルな値の宣言が簡単に行えるようになります。
const ( Sunday = iota; Monday; Tuesday; Wednesday; Thursday; Friday; Partyday; numberOfDays; // この定数はエクスポートされない )
Iota
定数の宣言で使用される事前宣言済み擬似定数iota
は、連続した型を持たない整数定数値を生成します。予約語const
がソース内に現れると値が0
にリセットされ、セミコロンが現れると値がひとつ増やされます。これは関連し合う定数グループを作成するときに利用されます。
const ( // iotaは0にリセットされる c0 = iota; // c0 == 0 c1 = iota; // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota; // a == 1 (iotaはリセット済み) b = 1 << iota; // b == 2 c = 1 << iota; // c == 4 ) const ( u = iota * 42; // u == 0 (型を持たない整数定数) v float = iota * 42; // v == 42.0 (浮動小数点定数) w = iota * 42; // w == 84 (型を持たない整数定数) ) const x = iota; // x == 0 (iotaはリセット済み) const y = iota; // y == 0 (iotaはリセット済み)
ひとつの式リスト内では、iota
の値は常に同じです。これはiota
の値はセミコロンが現れた時だけ増やされるためです。
const ( bit0, mask0 = 1 << iota, 1 << iota - 1; // bit0 == 1, mask0 == 0 bit1, mask1; // bit1 == 2, mask1 == 1 _, _; // スキップ iota == 2 bit3, mask3; // bit3 == 8, mask3 == 7 )
上の例は、直前の式リストを繰り返し適用し続けています。
型の宣言
型の宣言は型名となる識別子を、元になる型と同じ定義(エレメント、フィールド、チャネルの方向など)を持った新しい型としてバインドします。
また新しい型は、元になった型とは互換性がありますが異なる型です。
TypeDecl = "type" ( TypeSpec | "(" [ TypeSpecList ] ")" ) . TypeSpecList = TypeSpec { ";" TypeSpec } [ ";" ] . TypeSpec = identifier Type .
type IntArray [16]int type ( Point struct { x, y float }; Polar Point ) type TreeNode struct { left, right *TreeNode; value *Comparable; } type Cipher interface { BlockSize() int; Encrypt(src, dst []byte); Decrypt(src, dst []byte); }
宣言された型は、バインドした型からはメソッドを継承しません。しかしコンポジット型の要素が持っているメソッド群はそのまま残ります。
// MutexはLockとUnlockメソッドを持つデータ型 type Mutex struct { /* Mutex fields */ } func (m *Mutex) Lock() { /* Lock implementation */ } func (m *Mutex) Unlock() { /* Unlock implementation */ } // NewMutexはMutexと同じ構成ではあるが、メソッド群は空である type NewMutex Mutex // PrintableMutexのメソッド群には、匿名フィールドMutexに // バインドされているLockとUnlockメソッドが含まれる type PrintableMutex struct { Mutex; }
論理値型、数値型、文字列型から別の型を定義してメソッドを追加するために、型の宣言が使われることがあります。
type TimeZone int const ( EST TimeZone = -(5 + iota); CST; MST; PST; ) func (tz TimeZone) String() string { return fmt.Sprintf("GMT+%dh", tz); }
変数の宣言
変数は宣言によって作成、バインドされ、型と必要であれば初期値を与えられます。
VarDecl = "var" ( VarSpec | "(" [ VarSpecList ] ")" ) . VarSpecList = VarSpec { ";" VarSpec } [ ";" ] . VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int var U, V, W float var k = 0 var x, y float = -1, -2 var ( i int; u, v, s = 2.0, 3.0, "bar" ) var re, im = complexSqrt(-1) var _, found = entries[name]; // マップからの引当。存在するかどうかだけ知りたい
式リストが与えられたときは、変数にはその式が順番に代入 (§代入)され、初期化されます。そのときは、式リスト内のすべての式が代入に使用され、全ての変数がそれによって初期化される必要があります。式リストが与えられなければ、各変数はゼロ値で初期化されます。
型が指定されたときは、変数はその型になります。指定されないときは、代入された式から型が推測されます。
型が指定されず、かつ対応する式が型を持たない定数のときは、宣言された変数の型は、bool
、 int
、 float
、 string
のいずれかになります。どれになるかは定数の値に依存します。
var b = true // tはbool型 var i = 0 // iはint型 var f = 3.0 // fはfloat型 var s = "OMDB" // sはstring型
省略形式による変数の宣言
省略形式による変数の宣言には、下の文法が使われます。
ShortVarDecl = IdentifierList ":=" ExpressionList .
これは変数の宣言の省略形式で、初期化式は伴いますが型の指定は行いません。
"var" IdentifierList = ExpressionList .
i, j := 0, 10; f := func() int { return 7; } ch := make(chan int); r, w := os.Pipe(fd); // os.Pipe()は戻り値を2つ返す _, y, _ := coord(p); // coord()は戻り値を3つ返すが、y座標だけ使用する
非省略形式とは異なり、この省略形式による変数の宣言は、同じブロック内で、同じ型として宣言された変数に対して再宣言が可能です。ただし、このとき宣言する変数のうち少なくともひとつは、ブランク変数ではない新しい変数である必要があります。結果として、再宣言は複数の値に対してのみ適用できます。再宣言では新しい変数を作成するのではなく、単に変数に新しい値を割り当てます。
field1, offset := nextField(str, 0); field2, offset := nextField(str, offset); // offsetの再宣言
省略形式は関数内でのみ使用されます。また省略形式はif
、for
、switch
の初期化コンテキストにおいて、一時的なローカル変数を宣言するために用いられます(§ステートメント)。
関数の宣言
関数の宣言は、識別子と関数をバインドします(§関数型)。
FunctionDecl = "func" identifier Signature [ Body ] . Body = Block.
関数の宣言の際、関数の本体部分を省略することが可能です。このような宣言はアセンブリルーチンのようなGo言語の外で実装された関数へのシグネチャを定義します。
func min(x int, y int) int { if x < y { return x; } return y; } func flushICache(begin, end uintptr) // 外部にて実装
メソッドの宣言
メソッドの宣言は、識別子とメソッド(レシーバを伴った関数)をバインドします。
MethodDecl = "func" Receiver MethodName Signature [ Body ] . Receiver = "(" [ identifier ] [ "*" ] BaseTypeName ")" . BaseTypeName = identifier .
T
が型の名前とすると、レシーバの型はT
または*T
の形式でなければなりません。T
はレシーバベース型、または単にベース型と呼ばれます。このベース型はポインタまたはインタフェース型であってはならず、またメソッドと同じパッケージ内で宣言されていなくてはなりません。メソッドの宣言は、「メソッドをベース型にバインドする」とも言われます。メソッドはセレクタを使ってその型と一緒に指定したときのみ参照可能になります (§型の宣言、§セレクタ)。
Point
型にメソッドの宣言を行います。
func (p *Point) Length() float { return Math.sqrt(p.x * p.x + p.y * p.y); } func (p *Point) Scale(factor float) { p.x = p.x * factor; p.y = p.y * factor; }
レシーバ型が*Point
のLength
メソッドとScale
メソッドを、ベース型Point
にバインドしています。
レシーバの値がメソッドの本体部で参照されないのであれば、宣言時にレシーバの識別子を省略可能です。同じことは、一般に関数とメソッドのパラメータにもあてはまります。
メソッドの型は、第一パラメータにレシーバを伴った関数の型となります。例えばScale
メソッドは次の型になります。
(p *Point, factor float)
しかし、このように宣言しても関数はメソッドにはなりません。
Comments