ブログに戻る

Go の import キーワード:構文、エイリアス、ブランク import とドット import

Go の import キーワードの仕組みを解説します。import のグループ化、import パス、未使用の import がビルドを失敗させる理由、エイリアス、ブランク import とドット import、import の循環の解消方法を扱います。

Go の import キーワード:構文、エイリアス、ブランク import とドット import

import キーワードは、ほかのパッケージがエクスポートしている名前を Go のソースファイル内で使えるようにします。import は package 句の直後、ほかのどの宣言よりも前に書きます。それぞれの import は、"net/http" や "github.com/jackc/pgx/v5" のような import パスでパッケージを指定します。その後は http.ListenAndServe のように、パッケージ名を通してその中身にアクセスします。import にはエイリアス、ブランク識別子 _、ドット . を付けることもでき、それぞれファイル内でのパッケージ名の扱いを変えます(Go spec)。

要約

  • import は package の後、ほかのすべての宣言より前に書きます。ほとんどのファイルでは、import ( ... ) という 1 つのグループにまとめます。
  • import パスは文字列です。標準ライブラリのパスは "encoding/json" のように短く、それ以外は go.mod のモジュールパスから始まります。
  • 使われていない import はコンパイルエラーになり、"os" imported and not used と表示されます。goimports と gopls が import の追加と削除を自動で行います。
  • alias "path" は、1 つのファイルの中でだけパッケージの名前を変えます。html/template と text/template のように、2 つの import が同じ名前を持つときに使います。
  • _ "path" は、副作用のためだけにパッケージを import します。副作用はそのパッケージの init 関数で実行されます。データベースドライバーや net/http/pprof がこの方式です。
  • . "path" は、パッケージ名を付けずに名前を使えるようにします。スタイルガイドでは推奨されておらず、staticcheck は ST1001 として警告します。例外はごく一部のテストだけです。
  • Go は import の循環を認めません。共有する部分を新しいパッケージに切り出すか、使う側でインターフェースを定義すれば解消できます。
  • import のスコープはファイル単位です。同じパッケージの別のファイルがすでに import していても、各ファイルは自分が使うものを import します。

Go でパッケージを import するには?

import の後に、パッケージのパスをダブルクォートで囲んで書きます。1 つのパッケージだけが必要なファイルなら 1 行で書けます。複数必要なら、括弧でまとめたブロックを使います。

example.gogo
package main

import "fmt"

func main() {
	fmt.Println("ok")
}
example.gogo
package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type healthResponse struct {
	Status string `json:"status"`
}

func main() {
	http.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(healthResponse{Status: "ok"})
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

どちらの書き方も意味は同じで、実際の Go のコードはほとんどがまとめたブロックで書かれています。import は常に package 句の後、const、var、type、func のどれよりも前に置きます。それより下に書くと、パーサーは syntax error: imports must appear before other declarations で止まります。関数の中で import する方法もありません。

ブロック内の並び順はフォーマットツールが整えます。gofmt は、各グループの中の行を import パスの順に並べ替えます。多くのエディターが保存時に実行する goimports は、さらにブロックを空行で 2 つのグループに分けます。標準ライブラリが先で、それ以外が後です。

example.gogo
import (
	"context"
	"fmt"
	"net/http"

	"example.com/shop/billing"
	"github.com/jackc/pgx/v5"
)

並び順はプログラムの動作に影響しません。並べておくと差分が小さくなり、標準ライブラリ以外の依存がどれなのかもひと目でわかります。

Go の import パスとは?

import パスは、パッケージがどこにあるかを go コマンドに伝える文字列です。標準ライブラリのパッケージは、"fmt"、"net/http"、"crypto/rand" のように、最初の要素にドットを含まない短いパスを持ちます。それ以外のパッケージのパスは、すべてモジュールパスから始まります。

自分のパッケージは、go.mod で宣言したモジュールパスにディレクトリを足したパスで import します。go.mod に module example.com/shop とあれば、./billing にあるパッケージは "example.com/shop/billing" として import します。モジュールモードには "./billing" のような相対 import がないので、ローカルのパッケージも必ずフルパスで import します。

サードパーティのパッケージも同じ仕組みで、go コマンドが go.mod を通して解決します。

  1. go get github.com/jackc/pgx/v5 でモジュールをダウンロードし、go.mod に require の行を追加します。
  2. import ブロックに "github.com/jackc/pgx/v5" と書きます。
  3. 後で go mod tidy を実行すると、import が必要とするモジュールが追加され、どこからも import されなくなったモジュールが削除されます。

パスの最後の要素は通常パッケージ名と一致しますが、常にそうとは限りません。"github.com/jackc/pgx/v5" が提供するのは pgx というパッケージで、"gopkg.in/yaml.v3" が提供するのは yaml です。名前とパスの関係は Go の package キーワードで解説しています。

Go はなぜ未使用の import をコンパイルしないのか?

Go は、使われていない import を警告ではなくエラーとして扱います。もう呼び出していない "os" をファイルに残したままにすると、go build は止まります。

example.texttext
./main.go:5:2: "os" imported and not used

その理由は Go FAQ に書かれています。未使用の import はコンパイルを遅くし、プログラムに不要な依存関係を加えます。大きなコードベースでは、それが積み重なっていきます。警告は無視されがちなので、Go は代わりにエラーにしています。go mod tidy は go.mod に対して同じ掃除を行い、どこからも import されていない依存を削除します。

実際には、手で直すことはほとんどありません。goimports と、VS Code や GoLand の Go サポートを支える Go の言語サーバー gopls が、保存時に未使用の import を削除し、足りない import を追加します。デバッグ中に少しの間だけ import を残しておきたいときは、var _ = os.Exit のようにそのパッケージの何かをブランク識別子に代入します。この行はコミットする前に削除しましょう。

Go で import の名前を変えるには?

パスの前にエイリアスを書きます。エイリアスは、そのファイルの中でだけパッケージ名の代わりになります。

example.gogo
import htmltemplate "html/template"

よくある理由は名前の衝突です。html/template と text/template はどちらも名前が template で、メール送信のサービスでは両方が必要になることがよくあります。件名にはプレーンテキストを、本文には HTML エスケープされた出力を使うからです。エイリアスなしで両方を import すると、template redeclared in this block で失敗します。一方にエイリアスを付ければ解決します。

example.gogo
package main

import (
	htmltemplate "html/template"
	"os"
	"text/template"
)

func main() {
	subject := template.Must(template.New("subject").Parse("Your invoice {{.ID}}\n"))
	body := htmltemplate.Must(htmltemplate.New("body").Parse("<p>{{.Note}}</p>\n"))

	data := map[string]string{"ID": "INV-42", "Note": "<script>alert(1)</script>"}
	subject.Execute(os.Stdout, data)
	body.Execute(os.Stdout, data)
}

出力を見ると、2 つのパッケージが別々に存在する理由がわかります。件名は Your invoice INV-42 と出力されます。本文は html/template によってエスケープされ、<p>&lt;script&gt;alert(1)&lt;/script&gt;</p> と出力されます。

エイリアスは、多くのパッケージが同じ汎用的な最後の要素を持つ場合にも使われます。Kubernetes のコードは、どれも v1 という名前の複数のパッケージを import するので、corev1 "k8s.io/api/core/v1" や metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" のように書きます。衝突がないなら、本来の名前のままにしておきましょう。http. なら誰でもすぐにわかりますが、独自のエイリアスだと、読み手は意味を確かめるために import ブロックまで戻ることになります。

Go のブランク import _ は何をするのか?

ブランク import は、名前を束縛せずにパッケージを読み込みます。そのパッケージの関数などは呼び出せませんが、パッケージの初期化は行われます。パッケージレベルの変数が設定され、init 関数が実行されます。パッケージの中には、init でほかのパッケージに自分自身を登録することで役目を果たすものがあります。

いちばんよくある例は database/sql のドライバーです。

example.gogo
package main

import (
	"database/sql"
	"log"
	"os"

	_ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
	db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
}

pgx の stdlib パッケージは、init の中で sql.Register("pgx", ...) を呼び出します。自分のコードから直接使うのは database/sql だけです。それでもブランク import がなければドライバーは登録されず、sql.Open は sql: unknown driver "pgx" (forgotten import?) を返します。

実際のサービスでよく見かけるブランク import には、ほかにも次のようなものがあります。

importinit の処理
_ "github.com/jackc/pgx/v5/stdlib"pgx ドライバーを database/sql に登録する
_ "net/http/pprof"プロファイリング用の /debug/pprof/ ハンドラーを http.DefaultServeMux に追加する
_ "image/png"PNG デコーダーを登録し、image.Decode で PNG ファイルを読めるようにする
_ "time/tzdata"タイムゾーンデータベースを持たないコンテナのために、それをバイナリに埋め込む
_ "embed"実行時には何もしない。string や []byte の変数に //go:embed を使うには、この import が必要

embed の場合は、init の副作用ではなくツールチェーンのルールです。import を書かないと、ビルドは go:embed requires import "embed" (or import _ "embed", if package is not used) で失敗します。

初期化の順序は予測できます。import された各パッケージは、そのパッケージ自身の import も含めて、それを import するパッケージより先に完全に初期化されます。Go 1.21 からは、互いに関係のないパッケージ同士の順序も言語仕様で決まっており、import パスの順に初期化されます。1 つのパッケージの中では、まずパッケージレベルの変数が設定されます。その後、ファイルがコンパイラに渡された順に init 関数が実行されます。go コマンドはファイルをファイル名の順に並べます。main は最後に実行されます。複数のパッケージから import されているパッケージも、初期化されるのは 1 回だけです。

ブランク import は、main か、実際にその副作用を必要とするパッケージに書きましょう。ほかの人が import するライブラリに書いてはいけません。net/http/pprof をブランク import するライブラリは、それを使うすべてのプログラムで、望むかどうかにかかわらずプロファイリング用のエンドポイントを公開してしまいます。

Go でドット import を使うべきか?

ほぼ使うべきではありません。ドット import は、パッケージがエクスポートしている名前をファイルに取り込むので、パッケージ名を付けずに呼び出せるようになります。

example.gogo
import . "strings"

func normalizeEmail(email string) string {
	return ToLower(TrimSpace(email))
}

3 行の関数なら追うのは簡単です。しかし長いファイルでは、ToLower を見ても、それがこのパッケージで定義されたものなのか、どの import から来たものなのか判断できません。Go Code Review Comments はドット import を推奨しておらず、staticcheck も警告します。

example.texttext
main.go:3:8: should not use dot imports (ST1001)

唯一認められている使い方は、循環のせいでテスト対象のパッケージの外に置かなければならないテストです。Code Review Comments では、bar/testutil を import する package foo_test の例が挙げられています。この bar/testutil 自体が foo を import しています。foo をドット import すれば、そのテストをパッケージの内部にあるかのように書けます。Ginkgo や Gomega のように、マッチャーにドット import を使う方法をドキュメントに載せているテストフレームワークもあります。それ以外の場面では、パッケージ名を書きましょう。

Go の import の循環とは?

import の循環とは、2 つ以上のパッケージが直接、または連鎖を通して互いに import し合っている状態です。Go はこれを禁止しています。そのため billing が customers を import し、customers が billing を import していると、ビルド時に失敗します。

example.texttext
package example.com/shop/billing
	imports example.com/shop/customers from billing.go
	imports example.com/shop/billing from customers.go: import cycle not allowed

このルールがあるので、初期化の順序が明確に定まり、ビルドも速くなります。コンパイラは常に、依存するものをすべてコンパイルした後でそのパッケージをコンパイルできるからです。循環は設計についても手がかりになります。循環があるときは、2 つのパッケージが実は 1 つであるべきか、両方が 3 つ目のパッケージに置くべきものに依存していることがほとんどです。

よくある解決方法は 2 つあります。

  1. 共有する部分を専用のパッケージに移します。billing と customers の両方が CustomerID 型を必要とするなら、どちらも import しない小さな shop/ids や shop/domain パッケージに置きます。
  2. 使う側でインターフェースを定義します。billing が顧客のメールアドレスを調べるだけなら、必要なものを自分で宣言し、実際の実装は main から渡してもらえます。
example.gogo
package billing

import "context"

// CustomerLookup is the one thing billing needs from the customers package.
type CustomerLookup interface {
	Email(ctx context.Context, customerID string) (string, error)
}

type Service struct {
	customers CustomerLookup
}

func NewService(customers CustomerLookup) *Service {
	return &Service{customers: customers}
}

これで billing は customers を import しなくなり、customers は自由に billing を import できます。シグネチャが一致する Email メソッドを持つ型は、インターフェースの名前を書かなくてもそれを満たすので、依存の向きは一方向だけになります。

go コマンドは、package main にも関連するルールを適用します。package main はどのパッケージからも import できず、試すと import "example.com/shop/cmd/api" is a program, not an importable package というエラーになります。

Go の import のスコープはファイル単位か、パッケージ単位か?

import のスコープはファイル単位です。パッケージレベルのほかの名前はパッケージ内のすべてのファイルで共有されますが、import でパッケージ名が使えるようになるのは、その import を書いたファイルの中だけです。次の例では、server.go が log を import しています。

example.gogo
package main

import "log"

func main() {
	log.Println("server starting")
	runJobs()
}

そして同じパッケージの jobs.go は、import せずに log を呼び出しています。

example.gogo
package main

func runJobs() {
	log.Println("running jobs")
}

パッケージレベルの関数は共有されるので、server.go から runJobs は見えます。しかし log の import は共有されないので、ビルドは失敗します。

example.texttext
./jobs.go:4:2: undefined: log

import はファイルごとに書き、goimports が自動で追加してくれます。また、ファイル単位であるために、import の名前が、別のファイルにあるパッケージレベルの宣言と衝突することがあります。あるファイルで var log = ... と宣言し、別のファイルで log を import すると、log already declared through import of package log ("log") というエラーになります。

エイリアスのスコープもファイル単位です。あるファイルで mrand "math/rand/v2" と名前を変えても、パッケージ内のほかのファイルには影響しません。

Go には、そもそも import できるパッケージを制限する仕組みもあります。internal/ ディレクトリの下にあるパッケージは、internal の親ディレクトリ以下にあるコードからしか import できません。internal/ とパッケージをまたいだ可視性の仕組みは、Go の package キーワードで解説しています。

LevelUpGo で学ぶ

LevelUpGo では、ブラウザ上で本物の Go コードを実行する演習を通じて Go を学べます。Packages & Organization コースでは、エクスポートされる名前、init とパッケージの状態、Go モジュールと依存関係、そして go get や go mod tidy のような日常的に使うモジュールのコマンドを扱います。Go を始めたばかりなら、まず Go Basics から取り組んでください。残り 24 個の予約語については、Go のキーワード一覧:全 25 個の予約語を解説をご覧ください。

よくある質問

Go で import はキーワードですか?

はい。import は Go の 25 個の予約語の 1 つなので、変数名、関数名、パッケージ名には使えません。書けるのは、ファイルの先頭にある import 宣言の中だけです。

Go で関数の中でパッケージを import できますか?

できません。import を書けるのはファイルのトップレベル、つまり package 句の直後だけです。関数本体の中に import を書くと、syntax error: unexpected keyword import で失敗します。1 つの関数でしか使わないパッケージでも、ファイルの先頭で import してください。

Go でローカルのパッケージを import するには?

go.mod のモジュールパスの後に、パッケージのディレクトリを続けます。example.com/shop という名前のモジュールでは、./internal/pricing にあるパッケージを "example.com/shop/internal/pricing" として import します。Go モジュールは "./pricing" のような相対 import をサポートしていません。

Go の import _ と import . の違いは何ですか?

import _ "path" はパッケージの初期化を実行しますが、そのパッケージを参照する手段は得られません。データベースドライバーの登録のような副作用のために使います。import . "path" はその逆で、パッケージがエクスポートしているすべての名前をファイルに直接取り込み、パッケージ名なしで呼び出せるようにします。ブランク import は Go らしいコードでもよく使われますが、ドット import は推奨されていません。

Go で import の順序は重要ですか?

重要ではありません。コンパイラは import ブロック内の行の順序を気にしません。初期化の順序も、import の書き方ではなく依存関係のグラフで決まります。gofmt はパスの順に並べ替え、goimports は標準ライブラリとほかのパッケージを分けますが、これは読みやすさのためだけです。

出典

シニアエンジニアのように Go を書く

ブラウザで学べるインタラクティブなレッスン。最初のレッスンは無料です。

無料レッスンを試すまたは無料アカウントを作成