前書き

Goでは、定義済みの `+ init()`関数は、パッケージの他の部分の前に実行するコードを開始します。 このコードは、https://www.digitalocean.com/community/tutorials/importing-packages-in-go [package is import]が実行されるとすぐに実行され、アプリケーションを特定の状態で初期化する必要がある場合に使用できます。など、アプリケーションの起動に必要な特定の構成またはリソースのセットがある場合など。 また、特定のパッケージをインポートすることによりプログラムの状態を設定するために使用されるテクニックである「副作用をインポートする」ときにも使用されます。 これは、あるパッケージを別のパッケージに「+登録」して、プログラムがタスクの正しいコードを考慮していることを確認するためによく使用されます。

`+ init()`は便利なツールですが、見つけにくい ` init()`インスタンスがコードの実行順序に大きく影響するため、コードを読みにくくすることがあります。 このため、Goを初めて使用する開発者は、この関数のファセットを理解して、コードを記述するときに ` init()+`を読みやすい方法で使用できるようにすることが重要です。

このチュートリアルでは、特定のパッケージ変数のセットアップと初期化、1回の計算、および別のパッケージで使用するパッケージの登録のために、 `+ init()+`がどのように使用されるかを学習します。

前提条件

この記事の一部の例では、次のものが必要です。

  • Goのインストール方法に従ってセットアップされたGoワークスペースローカルプログラミング環境のセットアップ]。 このチュートリアルでは、次のファイル構造を使用します。

.
├── bin
│
└── src
   └── github.com
       └── gopherguides

`+ init()+`の宣言

`+ init()`関数を宣言するときはいつでも、Goはそのパッケージ内の他の何かより前にそれをロードして実行します。 これを実証するために、このセクションでは、 ` init()+`関数を定義する方法を説明し、パッケージの実行方法への影響を示します。

最初に、 `+ init()+`関数を使用しないコードの例として以下を見てみましょう:

main.go

package main

import "fmt"

var weekday string

func main() {
   fmt.Printf("Today is %s", weekday)
}

このプログラムでは、「+ weekday 」と呼ばれるグローバルhttps://www.digitalocean.com/community/tutorials/how-to-use-variables-and-constants-in-go [変数]を宣言しました。 デフォルトでは、 ` weekday +`の値は空の文字列です。

このコードを実行しましょう:

go run main.go

`+ weekday +`の値は空白なので、プログラムを実行すると、次の出力が得られます。

OutputToday is

`+ weekday `の値を現在の日付に初期化する ` init()`関数を導入することで、空の変数を埋めることができます。 次の強調表示された行を ` main.go +`に追加します。

main.go

package main

import
   "fmt"



var weekday string





func main() {
   fmt.Printf("Today is %s", weekday)
}

このコードでは、 + time +`パッケージをインポートして使用し、現在の曜日( `+ Now()。Weekday()。String()+)を取得してから、 `+ init()`を使用してその値で ` weekday +`を初期化します。

プログラムを実行すると、現在の平日が出力されます。

OutputToday is Monday

これは `+ init()`がどのように機能するかを示していますが、 ` init()+`のはるかに一般的な使用例は、パッケージのインポート時に使用することです。 これは、パッケージを使用する前にパッケージ内の特定のセットアップタスクを実行する必要がある場合に役立ちます。 これを実証するために、パッケージが意図したとおりに機能するために特定の初期化を必要とするプログラムを作成しましょう。

インポート時にパッケージを初期化する

最初に、https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go [slice]からランダムなクリーチャーを選択して出力するコードを記述します。 ただし、初期プログラムでは `+ init()`を使用しません。 これは、私たちが抱えている問題と、 ` init()+`が問題をどのように解決するかをよりよく示します。

`+ src / github.com / gopherguides / `ディレクトリ内から、次のコマンドで ` creature +`というフォルダーを作成します。

mkdir creature

`+ creature `フォルダー内で、 ` creature.go +`というファイルを作成します。

nano creature/creature.go

このファイルに、次の内容を追加します。

creature.go

package creature

import (
   "math/rand"
)

var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}

func Random() string {
   i := rand.Intn(len(creatures))
   return creatures[i]
}

このファイルは、値として初期化された海の生き物のセットを持つ「+ creatures 」という変数を定義します。 また、https://www.digitalocean.com/community/tutorials/understanding-package-visibility-in-go#exported-and-unexported-items [exported] ` Random `関数からランダムな値を返します。 ` creatures +`変数。

このファイルを保存して終了します。

次に、 `+ main()`関数を記述して ` creature `パッケージを呼び出すために使用する ` cmd +`パッケージを作成しましょう。

`+ creature `フォルダーを作成したのと同じファイルレベルで、次のコマンドを使用して ` cmd +`フォルダーを作成します。

mkdir cmd

`+ cmd `フォルダー内で、 ` main.go +`というファイルを作成します。

nano cmd/main.go

ファイルに次の内容を追加します。

cmd / main.go

package main

import (
   "fmt"

   "github.com/gopherguides/creature"
)

func main() {
   fmt.Println(creature.Random())
   fmt.Println(creature.Random())
   fmt.Println(creature.Random())
   fmt.Println(creature.Random())
}

ここで、 `+ creature `パッケージをインポートし、 ` main()`関数で、 ` creature.Random()+`関数を使用してランダムなクリーチャーを取得し、4回印刷しました。

保存して `+ main.go +`を終了します。

これでプログラム全体が作成されました。 ただし、このプログラムを実行する前に、コードが適切に機能するための構成ファイルをいくつか作成する必要があります。 Goはhttps://blog.golang.org/using-go-modules[Go Modules]を使用して、リソースをインポートするためのパッケージの依存関係を構成します。 これらのモジュールは、パッケージディレクトリに配置される構成ファイルであり、パッケージのインポート元をコンパイラに指示します。 モジュールについて学習することはこの記事の範囲外ですが、ほんの数行の設定を書いてこの例をローカルで動作させることができます。

`+ cmd `ディレクトリで、 ` go.mod +`という名前のファイルを作成します。

nano cmd/go.mod

ファイルが開いたら、次の内容に配置します。

cmd / go.mod

module github.com/gopherguides/cmd
replace github.com/gopherguides/creature => ../creature

このファイルの最初の行は、作成した `+ cmd `パッケージが実際には ` github.com / gopherguides / cmd `であることをコンパイラに伝えます。 2行目は、コンパイラに、「 github.com/gopherguides/creature+」がディスクの「+ .. / creature +」ディレクトリにローカルにあることを伝えます。

ファイルを保存して閉じます。 次に、 `+ creature `ディレクトリに ` go.mod +`ファイルを作成します:

nano creature/go.mod

ファイルに次のコード行を追加します。

クリーチャー/go.mod

module github.com/gopherguides/creature

これは、作成した `+ creature `パッケージが実際には ` github.com / gopherguides / creature `パッケージであることをコンパイラに伝えます。 これがないと、 ` cmd +`パッケージはこのパッケージのインポート元を知りません。

ファイルを保存して終了します。

これで、次のディレクトリ構造とファイルレイアウトができました。

├── cmd
│   ├── go.mod
│   └── main.go
└── creature
   ├── go.mod
   └── creature.go

すべての設定が完了したので、次のコマンドで `+ main +`プログラムを実行できます。

go run cmd/main.go

これは与えるでしょう:

Outputjellyfish
squid
squid
dolphin

このプログラムを実行すると、4つの値を受け取り、それらを出力しました。 プログラムを複数回実行すると、予想どおりのランダムな結果ではなく、常に同じ出力が得られることに気付くでしょう。 これは、 `+ rand `パッケージが単一の初期状態に対して同じ出力を一貫して生成する擬似乱数を作成するためです。 より乱数を増やすには、パッケージを「シード」するか、プログラムを実行するたびに初期状態が異なるように変化するソースを設定します。 Goでは、現在の時刻を使用して ` rand +`パッケージをシードするのが一般的です。

`+ creature +`パッケージにランダム機能を処理させるため、このファイルを開きます:

nano creature/creature.go

次の強調表示された行を `+ creature.go +`ファイルに追加します。

クリーチャー/creature.go

package creature

import (
   "math/rand"

)

var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}

func Random() string {

   i := rand.Intn(len(creatures))
   return creatures[i]
}

このコードでは、 `+ time `パッケージをインポートし、 ` Seed()+`を使用して現在の時刻をシードしました。 ファイルを保存して終了します。

ここで、プログラムを実行すると、ランダムな結果が得られます。

go run cmd/main.go
Output

プログラムを繰り返し実行し続けると、ランダムな結果が引き続き得られます。 しかし、これはまだコードの理想的な実装ではありません。+ creature.Random()+ `が呼び出されるたびに、 + rand.Seed(time.Nowを呼び出すことで `+ rand `パッケージを再シードするためです().UnixNano()) `もう一度。 再シードは、内部クロックが変更されていない場合、同じ初期値でシードする機会を増やします。これにより、ランダムパターンが繰り返される可能性があります。または、クロックが変更されるのをプログラムに待機させることにより、CPU処理時間が増加します。

これを修正するために、 `+ init()`関数を使用できます。 ` creature.go +`ファイルを更新しましょう:

nano creature/creature.go

次のコード行を追加します。

クリーチャー/creature.go

package creature

import (
   "math/rand"
   "time"
)

var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}





func Random() string {
   i := rand.Intn(len(creatures))
   return creatures[i]
}

`+ init()`関数を追加すると、コンパイラに ` creature `パッケージがインポートされたときに、 ` init()+`関数を1回実行して乱数生成用の単一のシードを提供するようコンパイラーに指示します。 これにより、必要以上にコードを実行することがなくなります。 ここでプログラムを実行すると、引き続きランダムな結果が得られます。

go run cmd/main.go
Output

このセクションでは、 `+ init()`を使用して、パッケージが使用される前に適切な計算または初期化が実行されるようにする方法を説明しました。 次に、パッケージで複数の ` init()+`ステートメントを使用する方法を見ていきます。

`+ init()+`の複数のインスタンス

1回しか宣言できない `+ main()`関数とは異なり、 ` init()`関数はパッケージ全体で複数回宣言できます。 ただし、複数の ` init()`を使用すると、どれが他よりも優先順位が高いかを判断するのが難しくなります。 このセクションでは、複数の ` init()+`ステートメントの制御を維持する方法を示します。

ほとんどの場合、 `+ init()+`関数は、発生した順序で実行されます。 次のコードを例としてみましょう。

main.go

package main

import "fmt"

func init() {
   fmt.Println("First init")
}

func init() {
   fmt.Println("Second init")
}

func init() {
   fmt.Println("Third init")
}

func init() {
   fmt.Println("Fourth init")
}

func main() {}

次のコマンドでプログラムを実行すると:

go run main.go

次の出力が表示されます。

OutputFirst init
Second init
Third init
Fourth init

各 `+ init()`は、コンパイラが検出した順序で実行されることに注意してください。 ただし、 ` init()+`関数が呼び出される順序を決定することは必ずしもそれほど簡単ではありません。

複数のファイルがあり、それぞれに独自の `+ init()`関数が宣言されている、より複雑なパッケージ構造を見てみましょう。 これを説明するために、 ` message +`という変数を共有して出力するプログラムを作成します。

前のセクションから「+ creature 」および「 cmd +」ディレクトリとそれらのコンテンツを削除し、次のディレクトリとファイル構造に置き換えます。

├── cmd
│   ├── a.go
│   ├── b.go
│   └── main.go
└── message
   └── message.go

それでは、各ファイルの内容を追加しましょう。 `+ a.go +`で、次の行を追加します。

cmd / a.go

package main

import (
   "fmt"

   "github.com/gopherguides/message"
)

func init() {
   fmt.Println("a ->", message.Message)
}

このファイルには、 `+ message `パッケージから ` message.Message `の値を出力する単一の ` init()+`関数が含まれています。

次に、次の内容を `+ b.go +`に追加します。

cmd / b.go

package main

import (
   "fmt"

   "github.com/gopherguides/message"
)

func init() {
   message.Message = "Hello"
   fmt.Println("b ->", message.Message)
}

「+ b.go 」には、「 message.Message 」の値を「 Hello 」に設定して出力する単一の「 init()+」関数があります。

次に、 `+ main.go +`を作成して次のようにします。

cmd / main.go

package main

func main() {}

このファイルは何も行いませんが、実行するプログラムのエントリポイントを提供します。

最後に、次のように `+ message.go +`ファイルを作成します:

message / message.go

package message

var Message string

`+ message `パッケージは、エクスポートされた ` Message +`変数を宣言します。

プログラムを実行するには、 `+ cmd +`ディレクトリから次のコマンドを実行します:

go run *.go

`+ main `パッケージを構成する ` cmd `フォルダーには複数のGoファイルがあるため、 ` cmd `フォルダー内のすべての ` .go `ファイルをコンパイルする必要があることをコンパイラーに伝える必要があります。 ` *。go `を使用すると、 ` .go `で終わる ` cmd `フォルダー内のすべてのファイルをロードするようコンパイラーに指示します。 ` go run main.go `のコマンドを発行した場合、 ` a.go `および ` b.go +`ファイルのコードが表示されないため、プログラムはコンパイルに失敗します。

これにより、次のような出力が得られます。

Outputa ->
b -> Hello

Package InitializationのGo言語仕様に従って、パッケージ内で複数のファイルが検出されると、アルファベット順に処理されます。 このため、最初に `+ a.go `から ` message.Message `を出力したとき、値は空白でした。 値は、「 b.go 」からの「 init()+」関数が実行されるまで初期化されませんでした。

`+ a.go `のファイル名を ` c.go +`に変更すると、異なる結果が得られます。

Outputb -> Hello
a -> Hello

現在、コンパイラは最初に `+ b.go `に遭遇します。そのため、 ` c.go `の ` init()`関数が実行されると、 ` message.Message `の値はすでに ` Hello +`で初期化されています。遭遇します。

この動作により、コードに問題が発生する可能性があります。 ソフトウェア開発ではファイル名を変更するのが一般的であり、 `+ init()`の処理方法により、ファイル名を変更すると ` init()`が処理される順序が変わる場合があります。 これは、プログラムの出力を変更するという望ましくない効果をもたらす可能性があります。 再現可能な初期化動作を保証するために、ビルドシステムは、同じパッケージに属する複数のファイルをレキシカルファイル名順にコンパイラーに提示することをお勧めします。 すべての ` init()+`関数が順番に読み込まれるようにする1つの方法は、それらをすべて1つのファイルで宣言することです。 これにより、ファイル名が変更されても順序が変更されなくなります。

`+ init()`関数の順序が変わらないことを保証することに加えて、_global variables_、つまりパッケージ内のどこからでもアクセスできる変数を使用して、パッケージ内の状態の管理を回避することも試みるべきです。 前のプログラムでは、 ` message.Message `変数はパッケージ全体で利用可能で、プログラムの状態を維持していました。 このアクセスのために、 ` init()+`ステートメントは変数を変更し、プログラムの予測可能性を不安定にすることができました。 これを回避するには、プログラムへのアクセスをできるだけ許可しながら、できるだけアクセスの少ない制御されたスペースで変数を使用してみてください。

1つのパッケージに複数の `+ init()`宣言を含めることができることがわかりました。 ただし、そうすると望ましくない効果が生じ、プログラムの読み取りや予測が困難になる場合があります。 複数の ` init()+`ステートメントを回避するか、すべてを1つのファイルに保持することで、ファイルが移動したり名前が変更されたりしてもプログラムの動作が変わらないことが保証されます。

次に、副作用を伴うインポートに `+ init()+`がどのように使用されるかを調べます。

副作用に `+ init()+`を使用する

Goでは、コンテンツではなくパッケージをインポートすることが望ましい場合がありますが、パッケージのインポート時に発生する副作用のためです。 これは多くの場合、インポートされたコードに他のコードの前に実行される `+ init()+`ステートメントがあることを意味し、開発者がプロ​​グラムの開始状態を操作できるようにします。 この手法は、副作用のために_インポートと呼ばれます。

副作用のインポートの一般的な使用例は、コードの_register_機能です。これにより、プログラムがコードのどの部分を使用する必要があるかをパッケージに知らせることができます。 たとえば、https://golang.org/pkg/image/ [+ image + package]では、 + image.Decode +`関数は、デコードしようとしている画像の形式を知る必要があります( `+ jpg ++ png +、 `+ gif `など)を実行する前に。 これを実現するには、まず、 ` init()+`ステートメントの副作用がある特定のプログラムをインポートします。

次のコードスニペットを使用して、 `+ .png `ファイルで ` image.Decode +`を使用しようとしているとします。

サンプルデコードスニペット

. . .
func decode(reader io.Reader) image.Rectangle {
   m, _, err := image.Decode(reader)
   if err != nil {
       log.Fatal(err)
   }
   return m.Bounds()
}
. . .

このコードを使用したプログラムは引き続きコンパイルされますが、 `+ png +`画像をデコードしようとするとエラーが発生します。

これを修正するには、最初に `+ image.Decode `の画像フォーマットを登録する必要があります。 幸いなことに、 ` image / png `パッケージには次の ` init()+`ステートメントが含まれています。

image / png / reader.go

func init() {
   image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

したがって、デコードスニペットに `+ image / png `をインポートすると、 ` image / png `の ` image.RegisterFormat()+`関数がコードの前に実行されます。

サンプルデコードスニペット

. . .

. . .

func decode(reader io.Reader) image.Rectangle {
   m, _, err := image.Decode(reader)
   if err != nil {
       log.Fatal(err)
   }
   return m.Bounds()
}

これは状態を設定し、 `+ image.Decode()`の ` png `バージョンが必要であることを登録します。 この登録は、「 image / png」をインポートする副作用として発生します。

+" image / png "+`の前にhttps://golang.org/ref/spec#Blank_identifier [空白識別子]( `+ _ +)があることに気づいたかもしれません。 Goではプログラム全体で使用されていないパッケージをインポートできないため、これが必要です。 空白の識別子を含めると、インポート自体の値が破棄されるため、インポートの副作用のみが発生します。 これは、コードで `+ image / png +`パッケージを呼び出さなくても、副作用のためにそれをインポートできることを意味します。

副作用のためにいつパッケージをインポートする必要があるかを知ることは重要です。 適切に登録しないと、プログラムはコンパイルされますが、実行時に正しく機能しなくなる可能性があります。 標準ライブラリのパッケージは、ドキュメントでこのタイプのインポートの必要性を宣言します。 副作用のためにインポートが必要なパッケージを作成する場合は、使用している `+ init()+`ステートメントが文書化されていることを確認して、パッケージをインポートするユーザーが適切に使用できるようにしてください。

結論

このチュートリアルでは、パッケージ内の残りのコードがロードされる前に `+ init()`関数がロードされ、目的の状態の初期化などのパッケージに対して特定のタスクを実行できることを学びました。 また、コンパイラが複数の ` init()`ステートメントを実行する順序は、コンパイラがソースファイルをロードする順序に依存することも学びました。 ` init()+`の詳細については、公式のhttps://golang.org/doc/effective_go.html#init[Golang documentation]を確認するか、https://github.comをご覧ください。 / golang / go / issues / 25885 [Goコミュニティでの機能に関する議論]。

関数の詳細については、https://www.digitalocean.com/community/tutorials/how-to-define-and-call-functions-in-go [Goで関数を定義して呼び出す方法]の記事、またはhttps://www.digitalocean.com/community/tutorial_series/how-to-code-in-go[GoシリーズのHow To Code全体]をご覧ください。