🐷

Go言語におけるブロックとスコープ

2020/09/25に公開

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 が魔法を使える範囲(スコープ)は、千葉県(ブロック)の南半分だけ、みたいな感じです。

ブロックとスコープは概念のレイヤーが違う

ブロックはソースコードの字句的・場所的な物理層(ソースコードのここからここまで等)に近いレイヤーの話で、スコープは識別子の宣言の内容まで踏み込んで考える(誰が誰の影響を受ける等)高レイヤーの話、と理解しておくとよいと思います。

参考

https://golang.org/ref/spec#Blocks
https://golang.org/ref/spec#Declarations_and_scope

Discussion