Go言語でのパッケージのインポート
以下の日本語訳のオプション1です:
イントロダクション
異なるプロジェクト間でコードを借り受け、共有する能力は、広く使われるプログラミング言語やオープンソースコミュニティ全体にとって基盤となります。コードの借り受けにより、プログラマは自分のニーズに特化したコードを書くためにほとんどの時間を費やすことができますが、しばしば新しいコードの一部が他の人にとって有用となります。その場合、再利用可能な部分をまとめてユニットとして組織化し、自らのチームや広くプログラミングコミュニティと共有することを決定することがあります。
Goでは、再利用可能なコードの基本単位は「パッケージ」と呼ばれます。最も単純なGoプログラムでも、それ自体が1つのパッケージであり、おそらく他の少なくとも1つのパッケージを使用します。このチュートリアルでは、2つの小さなプログラムを書きます。1つは標準ライブラリパッケージを使用してランダムな数値を生成し、もう1つは人気のあるサードパーティーパッケージを使用してUUIDを生成します。その後、オプションで、同じベース名を持つ2つの似たような標準ライブラリパッケージを比較し、両方のパッケージをインポートして使用するより長いプログラムを書きます。最後に、goimportsツールを使用してインポートの形式をどのように整形するかを確認します。
Note
前提条件
このチュートリアルを始める前に、Goをインストールする必要があります。自分のオペレーティングシステムに適したチュートリアルを読んでください。
- How To Install Go On Ubuntu 20.04
- How To Install Go and Set Up a Local Programming Environment on macOS
- How To Install Go and Set Up a Local Programming Environment on Windows 10
ステップ1 — 標準ライブラリパッケージの使用
多くの言語と同様に、Goには共通のタスクに使用できる再利用可能なコードの組み込みライブラリが付属しています。例えば、文字列のフォーマットや出力、HTTPリクエストの送信などを行うために独自のコードを書く必要はありません。Goの標準ライブラリには、これらのタスクやその他多くのタスクに対するパッケージがあります。
「Goで最初のプログラムを書く方法」のプログラムは、標準ライブラリのfmtとstringsパッケージを使用しました。ここでは、math/randパッケージを使ってランダムな数字を生成する別のプログラムを書いてみましょう。
nanoまたはお気に入りのテキストエディターで、random.goという新しいファイルを開く。
- nano random.go
ゼロから九までのランダムな整数を5つ表示するプログラムを作成しましょう。以下のコードをエディタに貼り付けてください。
package main
import "math/rand"
func main() {
for i := 0; i < 5; i++ {
println(rand.Intn(10))
}
}
このプログラムはmath/randパッケージをインポートし、そのベース名である”rand”を参照して使用します。この”rand”は、パッケージ内の各Goソースファイルの先頭にあるパッケージの宣言で表示される名前です。
forループの各繰り返しで、rand.Intn(10)が呼び出され、0から9までのランダムな整数(10は含まれない)が生成され、その整数がコンソールに出力されます。
Note
プログラムを保存してください。もしnanoを使っている場合、CTRL+Xを押してからYとENTERを押して変更を確定させてください。その後、プログラムを実行してください。
go run random.go
0から9までの整数を5つ見るべきです。
1 7 7 9 1
ランダムな数値生成器は動作しているようですが、プログラムを繰り返し実行すると、毎回同じ数字が表示されることに注意してください。新しいランダムな数値が表示されると期待されるのに、これはランダムな数値生成器を固有の値で初期化するためにrand.Seed()関数を呼び出さなかったためです。これを行わない場合、パッケージはrand.Seed(1)が呼び出されたかのように振る舞い、毎回同じ「ランダム」な数値を生成します。
毎回プログラムが実行されるたびに、数値生成器に一意の値をシードする必要があります。プログラマーはしばしば現在のタイムスタンプ(ナノ秒単位)を使用します。それを取得するためには、timeパッケージが必要です。再度エディタでrandom.goを開き、以下を貼り付けてください。
package main
import (
"math/rand"
"time"
)
func main() {
now := time.Now()
rand.Seed(now.UnixNano())
println("Numbers seeded using current date/time:", now.UnixNano())
for i := 0; i < 5; i++ {
println(rand.Intn(10))
}
}
複数のパッケージをインポートする際、括弧を使用してインポートのブロックを作成することができます。ブロックを使用することで、各行にインポートキーワードを繰り返さずに済むため、コードがより清潔になります。
最初に、time.Now()関数を使って現在のシステム時刻を取得します。これにより、Time構造体が返されます。次に、その時刻をrand.Seed()関数に渡します。この関数は64ビット整数(int64)を受け取るため、now構造体のTime.UnixNano()メソッドを使用して、時間をナノ秒単位で渡す必要があります。最後に、ランダムな数値生成器のシードとして使用する時刻を出力しています。
今、プログラムを保存して再実行してください。 (Ima, puroguramu o hozon shite saijikkō shite kudasai.)
- go run random.go
このような出力が表示されるはずです。
Numbers seeded using current date/time: 1674489465616954000 2 6 3 1 0
プログラムを複数回実行すると、毎回異なる整数が表示され、ランダムな数値生成器のシードに使用される一意の整数も表示されるはずです。
プログラムをもう一度編集して、シードの時間を使いやすい形式で出力するようにしましょう。最初のprintln()呼び出しを以下のように編集してください。
println("Numbers seeded using current date/time:", now.Format(time.StampNano))
今、あなたはTime.Format()メソッドを呼び出し、timeパッケージで定義された多くのフォーマットの1つを渡しています。 time.StampNano定数(const)は文字列であり、それをTime.Format()に渡すことで、月、日、およびナノ秒までの時間を印刷することができます。 プログラムを保存して実行するもう一回の時間。
go run random.go
Numbers seeded using current date/time: Jan 23 10:01:50.721413000 7 6 3 7 3
January 1, 1970から経過したナノ秒の数を表す巨大な整数を見るよりも、それはより素敵です。
もしもあなたのプログラムがランダムな整数ではなく、多くのプログラマがデプロイメント全体でデータの一意の識別子として使用しているUUIDが必要な場合はどうでしょうか。Goの標準ライブラリにはそのような生成パッケージはありませんが、コミュニティには存在しています。では、サードパーティのパッケージをダウンロードして使用する方法について見てみましょう。
ステップ2:サードパーティパッケージを使用する
UUIDを生成するための最も人気のあるパッケージの1つはgithub.com/google/uuidです。サードパーティのパッケージは常に完全修飾名で認識されます。この完全修飾名には、コードをホストするサイト(例:github.com)、それを開発するユーザーまたは組織(例:google)、そして基本名(例:uuid)が含まれています。パッケージをインポートする際、pkg.go.devでドキュメントを読む際、および他の場所で、そのパッケージの完全修飾名を使用します。ただし、コードステートメントでそれを参照する際には、基本名のみを使用します。
パッケージをダウンロードする前に、プログラムの依存関係とそのバージョンを管理するためにGoがモジュールを初期化する必要があります。モジュールを初期化するためには、go mod init コマンドを使用し、自分自身のパッケージの完全修飾名を渡す必要があります。例えば、自分のGitHubのユーザー名が「sammy」である場合、以下のようにモジュールを初期化することができます。
- go mod init github.com/sammy/random
これによってgo.modというファイルが作成されます。では、そのファイルを見てみましょう。
- cat go.mod
module github.com/sammy/random go 1.19
このファイルは、Goモジュールとして配布される任意のGoリポジトリのルートに表示される必要があります。少なくともモジュールの名前と必要なGoバージョンを定義する必要があります。ご自身のGoバージョンは、上記に表示されるものとは異なる場合があります。
このチュートリアルでは、モジュールの配布は行いませんが、サードパーティのパッケージをダウンロードして使用するにはこの手順が必要です。
今は、go getコマンドを使用して、サードパーティのUUIDモジュールをダウンロードしてください。
- go get github.com/google/uuid
これによって最新バージョンをダウンロードします。
go: downloading github.com/google/uuid v1.3.0 go: added github.com/google/uuid v1.3.0
パッケージは、ローカルディレクトリの$GOPATH/pkg/mod/に配置されます。シェルで$GOPATHが明示的に設定されていない場合、デフォルトの値は$HOME/goです。例えば、ローカルユーザーがsammyでmacOSを実行している場合、このパッケージは/Users/sammy/go/pkg/modにダウンロードされます。ダウンロードされたモジュールがGoに配置される場所を確認するには、go env GOMODCACHEを実行できます。
新しい依存関係のgo.modファイルを表示しましょう。
- cat /Users/sammy/go/pkg/mod/github.com/google/uuid@v1.3.0/go.mod
module github.com/google/uuid
このモジュールは、サードパーティの依存関係がないようです。Go標準ライブラリのみを使用しています。
モジュールのバージョンはそのディレクトリ名に含まれていることに注意してください。これにより、同じパッケージの複数のバージョンを1つのプログラム内でまたは異なるプログラム間で開発およびテストすることができます。
自分自身のgo.modファイルをもう一度確認してください。
- cat go.mod
module github.com/sammy/random go 1.19 require github.com/google/uuid v1.3.0 // indirect
現在のディレクトリ内にあるgo.modファイルを検知したgo getコマンドは、プログラムの新しい依存関係(バージョンも含む)を反映するためにそれを更新しました。これでパッケージを使用できるようになります。テキストエディタでuuid.goという新しいファイルを開いて、以下のコードを貼り付けてください。
package main
import "github.com/google/uuid"
func main() {
for i := 0; i < 5; i++ {
println(uuid.New().String())
}
}
このプログラムは、random.goと似ていますが、math/randを使用して5つの整数を印刷する代わりに、github.com/google/uuidを使用して5つのUUIDを印刷します。
新しいファイルを保存して実行してください。
- go run uuid.go
あなたの出力はこのように似ている必要があります:
243811a3-ddc6-4e26-9649-060622bba2b0 b8129aa1-3803-4dae-bd9f-6ba8817f44b2 3ae27c71-caa8-4eaa-b8e6-e629b7c1cb49 37e06706-004d-4504-ad37-03c68252bb0f a2da6904-a6ab-4cc2-849b-d9d25a86e373
github.com/google/uuidパッケージは、random.goで使用したmath/randパッケージと似ていますが異なるcrypto/randパッケージを使用して、これらのUUIDを生成します。もしも両方のパッケージを使用する必要がある場合はどうなるでしょうか?両方のパッケージはbase nameがrandとなっているため、コード内でそれぞれのパッケージを参照するにはどうすれば良いのでしょうか?それについて次に見てみましょう。
ステップ3 – 同じ名前のパッケージのインポート
math/randのドキュメントによれば、実際に擬似乱数を生成し、”セキュリティに関係する作業には適していない”と述べています。そのような作業には、代わりにcrypto/randを使用します。しかし、もし整数の乱数の品質がプログラムにとって重要でない場合はどうでしょうか?もしかしたら、単に任意の数値が必要なのかもしれません。
これら2つのrandパッケージのパフォーマンスを比較するプログラムを作成することができますが、そのプログラム内ではrandという名前で両方のパッケージを参照することはできません。この問題を解決するため、Goではパッケージをインポートする際に代替のローカル名を選ぶことができます。
同じ基本名を持つ2つのパッケージをインポートする方法は次のとおりです:
import (
“math/rand”
crand “crypto/rand”
)
好きなエイリアスを選ぶことができます(他のパッケージ名と重複していない限り)、そしてそれを完全修飾パッケージ名の左側に配置してください。この場合、エイリアスはcrandです。エイリアスには引用符はつきませんので注意してください。このインポートブロックを含むソースファイルの残りの部分では、選んだ名前でcrypto/randパッケージにアクセスすることができます。
自分自身の名前空間にもパッケージを取り込むことができます(別名として`.`を使用するか、別名なしで`_`を使用するか)。詳しくは、Goのドキュメントを読んでください。
同じ名前のパッケージを使用する方法を説明するために、両方のパッケージを使用してランダムな整数を生成し、それぞれの場合にかかる時間を計測する長いプログラムを作成し実行しましょう。このパートはオプションです。興味がない場合は、ステップ4に進んでください。
math/randとcrypto/randを比較する(オプション)
コマンドライン引数を取得する
作業ディレクトリにcompare.goという名前の3つ目の新しいファイルを開き、以下のプログラムを貼り付けてください。
package main
import "os"
import "strconv"
func main() {
// User must pass in number of integers to generate
if len(os.Args) < 2 {
println("Usage:\n")
println(" go run compare.go <numberOfInts>")
return
}
n, err := strconv.Atoi(os.Args[1])
if err != nil { // Maybe they didn't pass an integer
panic(err)
}
println("User asked for " + strconv.Itoa(n) + " integers.")
}
このコードは、後でrandパッケージを使用してユーザーが与えた数だけ疑似乱数の整数を生成するために準備します。osおよびstrconv標準ライブラリパッケージを使用して、単一のコマンドライン引数を整数に変換します。引数が渡されない場合は、使用方法の文を表示して終了します。
プログラムを実行し、引数として10を設定して動作を確認してください。
- go run compare.go 10
[seconary_label Output]
User asked for 10 integers.
今のところ、順調です。さて、前と同じようにmath/randパッケージを使用してランダムな整数を生成しましょうが、今回はそれを行うのにかかる時間を計算します。
フェーズ1:数学/乱数のパフォーマンスの測定
最後のprintln()ステートメントを削除し、以下のものと置き換えてください
// Phase 1 — Using math/rand
// Initialize the byte slice
b1 := make([]byte, n)
// Get the time
start := time.Now()
// Initialize the random number generator
rand.Seed(start.UnixNano())
// Generate the pseudo-random numbers
for i := 0; i < n; i++ {
b1[i] = byte(rand.Intn(256)) // Where the magic happens!
}
// Compute the elapsed time
elapsed := time.Now().Sub(start)
// In case n is very large, only print a few numbers
for i := 0; i < 5; i++ {
println(b1[i])
}
fmt.Printf("Time to generate %v pseudo-random numbers with math/rand: %v\n", n, elapsed)
最初に、ユーザーが要求した整数の数に基づいて、生成された整数(バイトとして)を保持するために、make()という組み込み関数を使用して空のバイトスライス([]byte)を作成しています。
それでは、現在の時刻を取得し、それをランダム数生成器のシードとして利用しています。Step 1 の random.go と同様に行っているのですね。
その後、0から255までのn個の疑似乱数の整数を生成し、それぞれをバイトに変換してバイトスライスに格納します。 なぜ0から255の整数を使うのかというと、書き換えようのある暗号/乱数生成コードでは、整数ではなくバイトを生成するため、パッケージを同等に比較する必要があるからです。 バイトは8ビットであり、0から255の範囲の符号なし整数で表現することができます(実際には、Goのバイト型はuint8型のエイリアスです)。
最終的に、もしユーザーが非常に大量の整数を要求した場合、最初の5バイトのみが印刷されます。一部の整数を見ることで、数値生成器が正常に動作していることを確認するのは良いですね。
プログラムを実行する前に、使用している新しいパッケージをインポートブロックに追加することを忘れないでください。
import (
"fmt"
"math/rand"
"os"
"strconv"
"time"
)
ハイライトされたパッケージを追加した後、プログラムを実行してください。
- go run compare.go 10
189 203 209 238 235 Time to generate 10 pseudo-random numbers with math/rand: 33.417µs
0から255までの間の10個の整数を生成し、それらをバイトスライスに格納するのに33.417マイクロ秒かかりました。それがcrypto/randのパフォーマンスと比較してどのような結果になるか見てみましょう。
フェーズ2 – 暗号化/乱数のパフォーマンス測定
以前に示されたように、crypto/randを使用するコードを追加する前に、パッケージをインポートブロックに追加してください。
import (
"fmt"
"math/rand"
crand "crypto/rand"
"os"
"strconv"
"time"
)
それでは、以下のコードをmain()関数の最後に追加してください。
// Phase 2 — Using crypto/rand
// Initialize the byte slice
b2 := make([]byte, n)
// Get the time (Note: no need to seed the random number generator)
start = time.Now()
// Generate the pseudo-random numbers
_, err = crand.Read(b2) // Where the magic happens!
// Compute the elapsed time
elapsed = time.Now().Sub(start)
// exit if error
if err != nil {
panic(err)
}
// In case n is very large, only print a few numbers
for i := 0; i < 5; i++ {
println(b2[i])
}
fmt.Printf("Time to generate %v pseudo-random numbers with crypto/rand: %v\n", n, elapsed)
このコードは、可能な限り第1フェーズのコードを精密に反映しています。サイズnのバイトスライスを生成し、現在の時刻を取得し、nバイトを生成し、経過時間を計算し、最後に5つの整数と経過時間を出力します。crypto/randパッケージを使用する際、乱数生成器を明示的にシードする必要はありません。
Note
あなたのcompare.goプログラムは、以下のようになるべきです。
package main
import (
"fmt"
"math/rand"
crand "crypto/rand"
"os"
"strconv"
"time"
)
func main() {
// User must pass in number of integers to generate
if len(os.Args) < 2 {
println("Usage:\n")
println(" go run compare.go <numberOfInts>")
return
}
n, err := strconv.Atoi(os.Args[1])
if err != nil { // Maybe they didn't pass an integer
panic(err)
}
// Phase 1 — Using math/rand
// Initialize the byte slice
b1 := make([]byte, n)
// Get the time
start := time.Now()
// Initialize the random number generator
rand.Seed(start.UnixNano())
// Generate the pseudo-random numbers
for i := 0; i < n; i++ {
b1[i] = byte(rand.Intn(256)) // Where the magic happens!
}
// Compute the elapsed time
elapsed := time.Now().Sub(start)
// In case n is very large, only print a few numbers
for i := 0; i < 5; i++ {
println(b1[i])
}
fmt.Printf("Time to generate %v pseudo-random numbers with math/rand: %v\n", n, elapsed)
// Phase 2 — Using crypto/rand
// Initialize the byte slice
b2 := make([]byte, n)
// Get the time (Note: no need to seed the random number generator)
start = time.Now()
// Generate the pseudo-random numbers
_, err = crand.Read(b2) // Where the magic happens!
// Compute the elapsed time
elapsed = time.Now().Sub(start)
// exit if error
if err != nil {
panic(err)
}
// In case n is very large, only print a few numbers
for i := 0; i < 5; i++ {
println(b2[i])
}
fmt.Printf("Time to generate %v pseudo-random numbers with crypto/rand: %v\n", n, elapsed)
}
各パッケージを使用して、10のパラメータを持つプログラムを実行して、10個の8ビット整数を生成してください。
- go run compare.go 10
145 65 231 211 250 Time to generate 10 pseudo-random numbers with math/rand: 32.5µs 101 188 250 45 208 Time to generate 10 pseudo-random numbers with crypto/rand: 42.667µs
この例の実行では、math/randパッケージはcrypto/randよりも少し速かったです。compare.goをパラメーター10で数回実行してみてください。それから、1000個または100万個の整数を生成してみてください。どのパッケージが一貫して速いですか?
このサンプルプログラムは、同じ名前で似た目的を持つ2つのパッケージを、同じプログラム内でどのように使用するかを示すためのものです。これは、これらのパッケージの一方を他方よりも優先する推奨ではありません。compare.goを拡張したい場合、各パッケージが生成するバイトのランダム性を比較するためにmath/statsパッケージを使用することができます。書いているプログラムによっては、異なるパッケージを評価し、必要に応じて最適なものを選択することが求められます。
最後に、goimportsツールを使用してimport宣言のフォーマット方法を見てみましょう。
ステップ4 — Goimportsを使用する
プログラミングのフローに没頭していると、使用しているパッケージをインポートするのを忘れたり、不要なパッケージを削除するのを忘れたりすることがあります。goimportsコマンドラインツールは、import宣言(とその他のコード)を整形するだけでなく、gofmtのより多機能な代替として、コードが参照するパッケージの不足しているインポートを追加したり、使用されていないパッケージのインポートを削除することも行います。
デフォルトでは、このツールはGoと一緒に配送されませんので、今すぐ go install を使用してインストールしてください。
- go install golang.org/x/tools/cmd/goimports@latest
以下の手順に従うと、goimportsバイナリが$GOPATH/binディレクトリにインストールされます。もしもあなたがmacOS用のGoのインストールとローカルプログラミング環境のセットアップのチュートリアル(またはあなたのオペレーティングシステムに対応したチュートリアル)をすでに実行している場合、このディレクトリは既に$PATHに含まれているはずです。以下のコマンドを試してみてください。
- goimports –help
もしもツールの使用方法のステートメントが表示されない場合、$GOPATH/binが$PATHに含まれていない可能性があります。お使いのオペレーティングシステムに適したGo環境の設定ガイドを読んで、それを設定してください。
goimportsを$PATHに設定したら、random.goからimportブロックをすべて削除してください。その後、goimportsを-dオプションとともに実行して追加する内容の差分を表示します。
- goimports -d random.go
diff -u random.go.orig random.go — random.go.orig 2023-01-25 16:29:38 +++ random.go 2023-01-25 16:29:38 @@ -1,5 +1,10 @@ package main +import ( + “math/rand” + “time” +) + func main() { now := time.Now() rand.Seed(now.UnixNano())
すごいですね、しかしgoimportsはローカルにインストールされている場合、サードパーティーパッケージも認識して追加できます。uuid.goからimportを削除し、それに対してgoimportsを実行してください。
- goimports -d uuid.go
diff -u uuid.go.orig uuid.go — uuid.go.orig 2023-01-25 16:32:56 +++ uuid.go 2023-01-25 16:32:56 @@ -1,8 +1,9 @@ package main +import “github.com/google/uuid” + func main() { for i := 0; i < 5; i++ { println(uuid.New().String()) } }
uuid.goを編集してください。そして、以下の作業を行ってください。
-
- import文にmath/randを追加し、コードは使用しない。
- 組み込みのprintln()関数をfmt.Println()に変更しますが、import “fmt”を追加しないようにしてください。
uuid.goの以下の部分を日本語で言い換えると、以下のようになります。
uuid.goのファイル
package main
import "math/rand"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(uuid.New().String())
}
}
ファイルを保存し、再度goimportsを実行してください。
- goimports -d uuid.go
diff -u uuid.go.orig uuid.go — uuid.go.orig 2023-01-25 16:36:28 +++ uuid.go 2023-01-25 16:36:28 @@ -1,10 +1,13 @@ package main -import “math/rand” +import ( + “fmt” + “github.com/google/uuid” +) + func main() { for i := 0; i < 5; i++ { fmt.Println(uuid.New().String()) } }
このツールは、不足しているインポートを追加するだけでなく、不要なインポートを削除します。また、各行にimportキーワードを使用するのではなく、インポートをかっこ内のブロックに置くことにも注意してください。
uuid.go(または任意のファイル)への変更をターミナルに出力するのではなく、goimportsコマンドを-wオプションとともに使用してuuid.goに変更を書き込んでください。
- goimports -w uuid.go
*.go ファイルを保存する際に、必ずgoimportsを呼び出すようにテキストエディタまたはIDEを設定するべきです。先に述べたように、goimportsはgofmtよりも優れているため、既にテキストエディタがgofmtを使用している場合は、代わりにgoimportsを使用するように設定してください。
もう1つのgoimportsの機能は、インポート文の特定の並び順を強制することです。インポート文の順序を手動で維持しようとすると、手間がかかり、エラーの原因となることがあります。そのため、goimportsに処理を任せることで、この問題を解決できます。
もしGoチームがGoソースファイルの公式形式を変更した場合、それらの変更を反映するためにgoimportsを更新します。そのため、常に最新の規格に合致するためには定期的にgoimportsを更新する必要があります。
結論
このチュートリアルでは、人気のあるパッケージを使用してランダムな数字やUUIDを生成するために10行未満の2つのプログラムを作成して実行しました。Goのエコシステムは、優れたパッケージが豊富なため、Goで新しいプログラムを書くことは楽しみであり、思っているよりも少ない努力で特定のニーズに合った便利なプログラムを作成することができます。
シリーズの次のチュートリアル、Goでパッケージを書く方法をチェックしてみてください。そして、お時間あれば、Goモジュールの使い方を見て、パッケージをグループ化し、他の人に一つのユニットとして配布する方法を理解してみてください。