Go言語におけるブロックとスコープ
Go言語には「ブロック」と「スコープ」という似たような2つの概念があります。
「ブロックとスコープはどう違うの?」という疑問に答えてみたいと思います。
ブロックとはソースコードの「かたまり」のこと
ブロックとはソースコードをある単位でまとめた「かたまり」です。
最もわかりやすい例は、記号 {
... }
でくくられたステートメント群です。
{
var int x = 1
println(x)
}
これを「明示的ブロック」と言います。
「明示的ブロック」以外にも「暗黙のブロック」というものがあります。
暗黙ブロックは以下のとおり5種類あります。
- ユニバースブロック: 全てのGoのソースコードの外側を包んでいる仮想的なブロック
- パッケージブロック: 1パッケージ内の全ソースコードを包含するブロック
- ファイルブロック: 1ファイル内の全ソースコードを包含するブロック
- if, for, switchブロック: if,for,swtich文それ自体がブロックとなる
- switch, selectにおける節ブロック: case節やdefault節自体がブロックとなる
ブロックは入れ子にすることができます。
if, for, switchブロック
if, for, switchブロックで注意が必要なのは、{...}
だけがブロックなのではなくて、if|for|switch
から }
までの全体が暗黙ブロックであるということです。
// if から } までが1個のブロック
if x := 1; x == 1 {
// { から } まで はさらにもう1個のブロック
var x int
println(x) // => 0
}
この例ではまず if ... {...}
という暗黙ブロックが1個あり、その内側にはさらに {...}
という明示的ブロックが1個あります。 (内側でxを再宣言できているのがその証拠です)
節ブロック
swtich文中のcase節やdefault節は、たとえ {...}
を書いてなくてもブロックを構成します。
switch "foo" {
case "a":
var x int = 1
println(x)
case "b":
var x float64 = 2.0
println(x)
default:
var x string = "3"
println(x)
}
このコードの各case節とdefault節は別々のブロックなので、識別子x
をそれぞれ独自に宣言することができます。
ブロック = 地図の塗り分け
現実世界で例えると、「日本」とか「千葉県」とか「浦安市」のように地図上の境界線で囲まれた領域に似ています。
地球(パッケージ)の外に宇宙(ユニバース)がある、とかそんな感じですね。
スコープとは、ある識別子の宣言が及ぶ範囲
スコープというのは、ある識別子 (x
とか Foo
とか) の宣言に着目したとき、その宣言が効力を持つ範囲のことです。
ブロックと似ていますが必ずしもブロック境界とイコールではありません。
例えば、ローカル変数を宣言したとき、その効力は「宣言の直後からブロック末尾まで」と定められています。
var x string
{
x = "hello"
println(x) // => "hello"
// --- ここまでの x は 外側で宣言された x
var x int // --- ここからブロックの末尾まで、 x は int
println(x) // => 0
}
println(x) // => "hello"
真ん中の var x int
の宣言の効力は、上の行には及びません。
x = "hello"
における x
は、外側のブロックで宣言されている x を指します。(外側で宣言されてなければコンパイルエラー)
スコープ = 登場人物xの魔法が効く範囲
例えていうと、ある魔法使い x
が魔法を使える範囲(スコープ)は、千葉県(ブロック)の南半分だけ、みたいな感じです。
ブロックとスコープは概念のレイヤーが違う
ブロックはソースコードの字句的・場所的な物理層(ソースコードのここからここまで等)に近いレイヤーの話で、スコープは識別子の宣言の内容まで踏み込んで考える(誰が誰の影響を受ける等)高レイヤーの話、と理解しておくとよいと思います。
参考
Discussion