Go言語入門:言語仕様 -Vol.41-

スポンサーリンク
Go言語入門:言語仕様 -Vol.41- 用語解説
Go言語入門:言語仕様 -Vol.41-
この記事は約8分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

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

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

ブランク識別子は、宣言の中で他の識別子と同様に使用できますが、束縛を導入しないため、宣言されたとはみなされません。パッケージブロックにおいて、識別子 initinit 関数の宣言にのみ使用でき、ブランク識別子と同様に新たな束縛を導入しません。

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

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

Goはブロックを用いたレキシカルスコープ(静的スコープ)を採用しています。

  1. 事前宣言された識別子のスコープは、ユニバースブロックです。
  2. トップレベル(関数の外側)で宣言された定数、型、変数、または関数(メソッドは除く)を表す識別子のスコープは、パッケージブロックです。
  3. インポートされたパッケージのパッケージ名のスコープは、そのインポート宣言を含むファイルのファイルブロックです。
  4. メソッドレシーバ、関数パラメータ、または戻り値の変数を表す識別子のスコープは、関数本体です。
  5. 関数の型パラメータ、またはメソッドレシーバによって宣言された型パラメータを表す識別子のスコープは、関数名の直後から始まり、関数本体の終わりで終了します。
  6. 型の型パラメータを表す識別子のスコープは、型名の直後から始まり、TypeSpecの終わりで終了します。
  7. 関数内部で宣言された定数または変数の識別子のスコープは、ConstSpecまたはVarSpec(短縮変数宣言の場合はShortVarDecl)の終わりから始まり、それを含む最も内側のブロックの終わりで終了します。
  8. 関数内部で宣言された型識別子のスコープは、TypeSpec内のその識別子の位置から始まり、それを含む最も内側のブロックの終わりで終了します。

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

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


解説

たとえ話

会社のオフィスに例えてみましょう。

「宣言」は、名札を作って人や物に貼る行為です。オフィスのすべての物には名札がなければなりません(=すべての識別子は宣言が必要)。同じ部屋に同じ名前の名札を2枚貼ることはできません(=同一ブロック内での重複宣言は不可)。

「スコープ」は、その名札が通用する範囲です。

  • ユニバースブロック → 会社全体の共通備品(truefalseint など)。どのフロア・どの部屋でも通じます。
  • パッケージブロック → フロア全体の共有物。そのフロアのどの部屋からでも使えます。
  • ファイルブロック → ある部屋にだけ届いた郵便物(import したパッケージ名)。隣の部屋では通じません。
  • 関数ブロック → 部屋の中のデスク。そのデスクでだけ使える私物です。

そして、**ブランク識別子(_)は「使い捨ての付箋」**です。名前を書いて貼りますが、後から参照する目的がないため、名札としては登録されません。

コード例

package main // パッケージ節:宣言ではない。スコープに入らない

import "fmt" // "fmt" のスコープはこのファイルブロックのみ

// トップレベル宣言 → スコープはパッケージブロック全体
const MaxRetry = 3

// トップレベル関数 → スコープはパッケージブロック全体
func greet(name string) string {
	// "name" のスコープは関数本体
	// 関数内で宣言した変数のスコープは、宣言位置から最も内側のブロックの終わりまで
	greeting := "こんにちは、" + name
	return greeting
}

func main() {
	fmt.Println(greet("Gopher"))
	fmt.Println(MaxRetry) // ✅ パッケージブロックなのでどこからでもOK
}
package main

import "fmt"

func main() {
	// ── スコープ規則7:変数のスコープは「宣言の終わり」から始まる ──
	x := 10 // x のスコープはここから main の } まで

	if x > 5 {
		// ── 内側のブロックで同名を再宣言(シャドウイング) ──
		x := 999 // 新しい x。外側の x = 10 は隠される
		fmt.Println("内側の x:", x) // → 999
	}

	fmt.Println("外側の x:", x) // → 10(元の x に戻る)
}
package main

import "fmt"

// ── ブランク識別子と init の特殊性 ──

func init() {
	// init は特殊関数:束縛を導入しない
	// 複数の init を同一パッケージに書ける(通常の関数では不可能)
	fmt.Println("初期化処理を実行中...")
}

func main() {
	// ブランク識別子 _ は「受け取るが使わない」
	val, _ := twoValues() // 2つ目の戻り値を意図的に捨てる
	fmt.Println(val)
}

func twoValues() (int, int) {
	return 42, 99
}
package main

import "fmt"

// ── 型パラメータのスコープ(Go 1.18以降のジェネリクス) ──

// T のスコープは "Print" の名前の直後から関数本体の終わりまで
func Print[T any](value T) {
	fmt.Println(value)
}

// T のスコープは "Stack" の名前の直後から TypeSpec の終わりまで
type Stack[T any] struct {
	items []T
}

func main() {
	Print(42)
	Print("hello")
}

よくある間違い・注意点

1. import したパッケージ名がファイルをまたがない

// file_a.go
package myapp
import "fmt" // "fmt" はこのファイルでだけ有効

// file_b.go
package myapp
// fmt.Println(...) // ❌ ここでは fmt を import していないのでエラー

ファイルごとに必要な import を書く必要があります。

2. 変数のスコープの「開始位置」に注意

スコープは宣言の終わりから始まります。つまり、変数は自分自身の初期化式の中では使えません。

// ❌ 自分自身を初期化式で参照はできない
// x := x + 1 // x はまだスコープに入っていない

3. パッケージ節(package main)は宣言ではない

package main と書いても、main という名前がスコープに登録されるわけではありません。あくまでファイルの所属を示すラベルです。これは意外と見落としがちなポイントです。

4. init_ の特殊ルール

init 関数は名前の束縛を作りません。そのため、同じパッケージ・同じファイルに複数の init を書くことができます。通常の関数では同じ名前を重複して宣言するとエラーになりますが、init は例外です。

まとめ

  • 宣言とは、識別子(名前)を定数・型・変数・関数などに紐づけること。すべての識別子は宣言が必要で、同じブロック内での重複は許されない。
  • スコープとは、その識別子が有効な範囲のこと。Goはブロック構造に基づくレキシカルスコープを採用している。
  • スコープの階層は「ユニバース → パッケージ → ファイル → 関数 → 内部ブロック」と入れ子になっている。
  • _(ブランク識別子)と init は特殊で、束縛を導入しない。
  • 内側のブロックで同名の識別子を再宣言するとシャドウイングが起き、外側の識別子が一時的に隠される。

前回の「ブロック」の解説と合わせて読むと、Goの変数がどの範囲で見えるのかがより明確になります。この仕組みを理解しておくと、「なぜコンパイルエラーになるのか」がすぐに分かるようになりますよ!

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました