Go/プログラムの初期化と実行

< Go


プログラムの初期化と実行

編集

プログラムの初期化と実行(Program initialization and execution)[1]

ゼロ値

編集

ゼロ値(The zero value) -- 宣言やnewの呼び出しによって変数にストレージが割り当てられたとき、あるいは複合リテラルやmakeの呼び出しによって新しい値が生成されたとき、明示的な初期化が行われなければ、その変数や値にはデフォルト値が与えられます。このような変数や値の各要素には、その型に応じたゼロ値が設定されます。ブーリアンはfalse、数値型は0、文字列は""、ポインタ、関数、インターフェース、スライス、チャンネル、マップはnilとなります。この初期化は再帰的に行われるので、例えば、構造体の配列の各要素は、値が指定されていない場合、そのフィールドがゼロになります[2]

この2つの単純な宣言は等価です。

var i int
var i int = 0

上を踏まえて

type T struct { i int; f float64; next *T }
t := new(T)

とすると、次のようになります。

t.i == 0
t.f == 0.0
t.next == nil

また、次のようにしても同じことが言えます。

var t T

パッケージの初期化

編集

パッケージの初期化(Package initialization) -- パッケージ内では、パッケージレベルの変数の初期化が段階的に行われます。各段階では、未初期化の変数に依存していない、宣言順で最も早い変数が選択されます[3]

より正確には、パッケージレベルの変数がまだ初期化されておらず、初期化式を持たないか、初期化式が未初期化の変数に依存していない場合、初期化の準備ができているとみなされます。初期化は、初期化可能な変数がなくなるまで、宣言順序が最も早く、初期化可能な次のパッケージレベルの変数を繰り返し初期化することで進行します。

この処理が終了した時点でまだ初期化されていない変数がある場合、その変数は1つ以上の初期化サイクルの一部であり、プログラムは有効ではありません。

右辺の単一(多値)式で初期化された変数宣言の左辺にある複数の変数は、まとめて初期化されます。左辺の変数のいずれかが初期化された場合、それらの変数はすべて同じステップで初期化されます。

var x = a
var a, b = f() // xが初期化される前に、aとbが一緒に初期化される

パッケージの初期化のために、ブランク変数は宣言の中で他の変数と同じように扱われます。

複数のファイルで宣言された変数の宣言順序は、ファイルがコンパイラーに提示される順序によって決まります。最初のファイルで宣言された変数は、2番目のファイルで宣言されたどの変数よりも先に宣言され、以下同様です。

依存性分析は、変数の実際の値には依存せず、ソース中の変数への字句の参照のみを過渡的に分析します。例えば、変数xの初期化式が、本体が変数yを参照している関数を参照している場合、xはyに依存していることになります。

  • 変数や関数への参照は、その変数や関数を示す識別子です。
  • メソッドmへの参照は、t.mという形式のメソッド値またはメソッド式で、tの(静的)型はインターフェース型ではなく、メソッドmtのメソッドセットに含まれます。
  • 変数、関数、またはメソッド x は、x の初期化式または本体(関数やメソッドの場合)に y への参照、または y に依存する関数やメソッドへの参照が含まれている場合、変数 y に依存します。

例えば、次のような宣言があるとします。

var (
	a = c + b  // == 9
	b = f()    // == 4
	c = f()    // == 5
	d = 3      // 初期化終了後は5
)

func f() int {
	d++
	return d 
}

の場合、初期化の順序は d, b, c, a となります。なお、初期化式の部分式の順序は関係ありません。a = c + ba = b + cは、この例では同じ初期化順序になります。

依存関係の分析はパッケージごとに行われます。現在のパッケージで宣言された変数、関数、(インターフェイスではない)メソッドを参照しているものだけが考慮されます。変数間に他の隠れたデータ依存性が存在する場合、それらの変数間の初期化順序は特定されません。

たとえば,次のような宣言があったとします。

var x = I(T{}).ab()   // xはaとbに検出されない隠れた依存関係がある
var _ = sideEffect()  // x, a, b には関係ない。
var a = b
var b = 42

type I interface      { ab() []int }
type T struct{}
func (T) ab() []int   { return []int{a, b} }

変数 ab の後に初期化されますが、xb の前に初期化されるのか、ba の間に初期化されるのか、a の後に初期化されるのか、したがって sideEffect() が呼び出される瞬間(x が初期化される前か後か)も指定されていません。

init関数

編集

変数は、パッケージブロックで宣言された init という名前の関数を使って、引数や結果のパラメータを指定せずに初期化することもできます。

func init() {  }

このような関数は、1つのソース・ファイル内であっても、パッケージごとに複数定義することができます。パッケージ・ブロックでは、init識別子はinit関数を宣言するためにのみ使用できますが、 識別子自体は宣言されません。したがって、init関数はプログラムのどこからも参照できません。

インポートのないパッケージは、パッケージレベルの変数に初期値を割り当てた後、すべての init関数を、コンパイラに提示された順に呼び出すことで初期化されます (複数のファイルに分かれている場合もあります)。パッケージがインポートされている場合、インポートされたパッケージは、パッケージ自体を初期化する前に初期化されます。複数のパッケージがあるパッケージをインポートしている場合、インポートされたパッケージは一度だけ初期化されます。パッケージのインポートは、構造上、周期的な初期化の依存関係が存在しないことを保証します。

パッケージの初期化(変数の初期化とinit関数の呼び出し)は、単一のゴルーチンで、一度に一つのパッケージを順番に実行します。init関数は、他のゴルーチンを起動することができ、初期化コードと同時に実行することができます。しかし、初期化は常にinit関数を順番に実行し、前の関数が戻るまで次の関数を起動しません。

再現性のある初期化動作を保証するために、ビルドシステムは、同じパッケージに属する複数のファイルを、辞書的なファイル名の順序でコンパイラに提示することが推奨されます。

プログラムの実行

編集

プログラムの実行(Program execution) -- 完全なプログラムは、mainパッケージと呼ばれる単一の非インポートパッケージと、そのパッケージがインポートするすべてのパッケージを過渡的にリンクすることで作成されます。mainパッケージはパッケージ名mainを持ち、引数を取らず値を返さない関数mainを宣言しなければならない。

func main() {  }

プログラムの実行は、mainパッケージを初期化し、関数mainを呼び出すことから始まります。その関数の呼び出しが戻ると、プログラムは終了します。

他の(mainではない)goroutines が完了するのを待つことはありません。

脚註

編集
  1. ^ “Program initialization and execution¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Program_initialization_and_execution. 
  2. ^ “The zero value¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#The_zero_value. 
  3. ^ “Package initialization¶”. The Go Programming Language Specification. The Go website. (Jul 26, 2021). https://golang.org/ref/spec#Package_initialization.