ブログに戻る

Go の package キーワード:package main、命名規則、使用例

Go のファイルはすべて package 句から始まります。package main の役割、パッケージ名とディレクトリや import パスの関係、Go の命名規則を解説します。

Go の package キーワード:package main、命名規則、使用例

package キーワードは、Go のソースファイルが属するパッケージの名前を指定します。すべての .go ファイルで、最初のコード行に書く必要があります。package main は実行可能なプログラムとしてビルドされ、package billing のようにそれ以外の名前を付けると、ほかのパッケージから import されるライブラリとしてビルドされます。1 つのディレクトリにあるファイルはすべて同じパッケージ名を使う必要があります。billing.Total のように、ほかのコードはその名前の後にドットを付けて、エクスポートされた名前にアクセスします(Go spec)。

要約

  • .go ファイルはすべて package name から始まります。その前に置けるのはコメントと空行だけです。
  • package main がプログラム本体です。引数も戻り値もない func main() が必要で、go build でバイナリになります。import することはできません。
  • 1 つのディレクトリが 1 つのパッケージです。同じフォルダに異なるパッケージ名のファイルがあると、found packages billing (invoice.go) and payments (tax.go) で失敗します。主な例外は、テストファイルで使う _test パッケージです。
  • パッケージ名と import パスは別物です。"gopkg.in/yaml.v3" を import して、yaml.Unmarshal を呼び出します。慣習では、名前をパスの最後の要素と一致させます。
  • 良いパッケージ名は、短い小文字の 1 単語です。アンダースコアも mixedCaps も使わず、util や common も避けます。
  • 可視性はパッケージ単位で決まります。大文字で始まる名前は、ほかのパッケージにエクスポートされます。小文字で始まる名前は、同じパッケージ内のすべてのファイルから見えますが、それ以外からは見えません。

Go の package main の役割とは?

package main は、ライブラリではなく実行ファイルをビルドするよう Go のツールチェーンに指示します。そのパッケージの main 関数がプログラムの開始点で、この関数が戻るとプログラムは終了します。次のコードは、1 ファイルで完結する HTTP サービスです。

example.gogo
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "ok")
	})
	log.Fatal(http.ListenAndServe(":8080", mux))
}

go run . でコンパイルして実行でき、go build -o bin/api . でバイナリを書き出せます。main に関する 2 つのルールは、どちらもツールチェーンが強制します。main 関数のない package main は、リンクの段階で失敗します。

example.texttext
runtime.main_main·f: function main is undeclared in the main package

また、go run ./billing でライブラリパッケージを実行しようとすると、コンパイルが始まる前に失敗します。

example.texttext
package example.com/shop/billing is not a main package

main はプログラムそのものなので、ほかのパッケージから import することはできません。試すと import "example.com/shop/cmd/api" is a program, not an importable package というエラーになります。そのため Go のプロジェクトでは、ロジックをライブラリパッケージに置き、main を小さく保ちます。複数のバイナリを持つリポジトリでは、それぞれを専用のディレクトリ(通常は cmd/ の下)に置き、各ディレクトリが独立した package main になります。LevelUpGo 自身のバックエンドには、API サーバーからコンテンツインポーターまで 11 個の package main があり、すべてが internal/ 以下の 22 個のライブラリパッケージを共有しています。

同じディレクトリのファイルで異なるパッケージ名を使えるか?

使えません。go コマンドはディレクトリを 1 つのパッケージとして扱うので、その中の .go ファイルはすべて同じ名前を宣言する必要があります。たとえば billing/invoice.go が package billing で始まっていて、誰かが package payments と書いた billing/tax.go を追加したとします。go build ./billing は次のエラーで止まります。

example.texttext
found packages billing (invoice.go) and payments (tax.go) in /home/dev/shop/billing

修正するには、どちらかの package 句の名前を変えるか、ファイルを専用のディレクトリに移します。ディレクトリは import できる最小の単位でもあります。2 つのファイルが別々のパッケージに属するなら、フォルダも分ける必要があります。

一方、1 つのパッケージを複数のファイルに分けても、何のコストもかかりません。ディレクトリ内の各ファイルからは、エクスポートされているかどうかにかかわらず、ほかのファイルの名前が import なしで見えます。invoice.go、tax.go、refund.go からなる billing パッケージは、1 つの長いファイルとまったく同じように振る舞います。

パッケージ名と import パスの違いとは?

import パスはパッケージの場所を表し、パッケージ名はコードの中で使う識別子です。import パスは go.mod のモジュールパスにディレクトリを足したもので、example.com/shop/billing のようになります。パッケージ名は、そのディレクトリ内のファイルにある package 句で決まります。

慣習では両者をそろえるので、名前はパスの最後の要素になります。次の表は、両者が一致するパスを 1 つと、よくある 2 つの例外を示しています。例外はどちらもバージョンが原因です。

import パスパッケージ名異なる理由
net/httphttp一致している
gopkg.in/yaml.v3yamlパスに .v3 というバージョンの接尾辞が付いている
math/rand/v2randメジャーバージョンのディレクトリ /v2
github.com/jackc/pgx/v5pgxメジャーバージョンのディレクトリ /v5

2 つの import が同じ名前を持つ場合は、import ブロックで一方の名前を変えます。セッショントークンを生成し、リトライのジッターも加えるコードでは、crypto/rand と math/rand/v2 の両方が必要になることがよくあります。どちらも名前は rand です。

example.gogo
package tokens

import (
	"crypto/rand"
	"encoding/hex"
	mrand "math/rand/v2"
	"time"
)

// NewSessionID returns a random 32-character hex session ID.
func NewSessionID() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// RetryDelay adds up to 100ms of jitter to a base delay.
func RetryDelay(base time.Duration) time.Duration {
	return base + time.Duration(mrand.IntN(100))*time.Millisecond
}

この名前の変更は、そのファイルの中だけで有効です。ほかのファイルからは、パッケージが宣言した名前のまま見えます。エイリアスやブランク import など、ほかの import の書き方は Go の import キーワードで解説しています。

フォルダ名と異なる名前のパッケージをディレクトリに置いても、Go はコンパイルします。そのしわ寄せは読む人に来ます。example.com/shop/httpapi-v2 を import した人は、パッケージ名が httpapi だと知るためにソースを開かなければなりません。バージョンの接尾辞のために違いが避けられない場合を除き、両者はそろえておきましょう。

Go のパッケージ名はどう付けるべきか?

Go のパッケージ名には、Effective Go と Go ブログの記事 Package names に基づく、いくつかの短いルールがあります。

  • 小文字の 1 単語にする。billing、httpapi、ratelimit のようにします。rate_limit や rateLimit は使いません。パッケージ名は任意の識別子でよいので言語仕様上は許されていますが、Go らしいコードでは使いません。
  • 短く具体的にする。名前はすべての呼び出し箇所で入力します。stringconversion より strconv、authenticationservice より auth のほうが適しています。
  • 提供するものにちなんで名付ける。util、common、helpers、misc、models は、中に何があるかを何も伝えません。そして、あらゆるパッケージから import される大きなパッケージに育ってしまいます。代わりに money、slugify、pagination のように目的ごとに分割します。
  • エクスポートする名前でパッケージ名を繰り返さない。呼び出し側は必ずパッケージ名を書くからです。http.Server は正しく、http.HTTPServer は誤りです。billing.Total は正しく、billing.BillingTotal は誤りです。パッケージの主要な型のコンストラクタは、ring.New のように単に New とすることがよくあります。
  • キーワードや _ を使わない。package type は構文エラーになり、package _ は invalid package name _ で失敗します。

名前は、エクスポートされた識別子と組み合わせて読んでみてください。呼び出し側からは必ずその形で見えるからです。ratelimit.New(100, time.Minute) や config.Load("app.yaml") は文章のように読めますが、utils.NewRateLimiterUtil はそうではありません。

Go の package は可視性をどう制御するのか?

Go には public、private、protected といったキーワードがありません。その代わりに、パッケージが境界になります。大文字で始まる名前はエクスポートされ、それを import するどのパッケージからも見えます。小文字で始まる名前はエクスポートされず、自身のパッケージの中でだけ、そのすべてのファイルから見えます。

example.gogo
// Package billing calculates invoice totals and applies VAT.
package billing

// VATPercent is the standard VAT rate applied to invoices.
const VATPercent = 25

// Total returns the sum of line items in cents, including VAT.
func Total(lineItems []int64) int64 {
	var sum int64
	for _, c := range lineItems {
		sum += c
	}
	return addVAT(sum)
}

func addVAT(cents int64) int64 {
	return cents + cents*VATPercent/100
}

package main のハンドラーは billing.Total を呼び出せますが、billing.addVAT(1000) は name addVAT not exported by package billing で失敗します。同じルールは struct のフィールドとメソッドにも当てはまります。encoding/json が struct のエクスポートされたフィールドしか扱えないのも、同じ理由です。

自分の複数のパッケージで共有したいものの、外部のモジュールからは import させたくないコードのために、Go には internal ディレクトリがあります。example.com/shop/internal/pricing にあるパッケージは、example.com/shop 以下にあるものからは import できますが、それ以外からはできません。別のモジュールが import しようとすると、次のエラーになります。

example.texttext
use of internal package example.com/shop/internal/pricing not allowed

internal/ を使うべき場面や、実際のサービスで cmd/ とライブラリパッケージをどう配置するかは、Go のプロジェクト構成で解説しています。

Go の _test パッケージとは?

_test.go で終わるテストファイルでは、2 つのパッケージ名のどちらかを使えます。package billing にすると、テストはパッケージの内部に置かれるので、エクスポートされていない関数も呼び出せます。

example.gogo
package billing

import "testing"

func TestAddVAT(t *testing.T) {
	if got := addVAT(1000); got != 1250 {
		t.Fatalf("addVAT(1000) = %d, want 1250", got)
	}
}

package billing_test にすると、外部テストパッケージになります。同じディレクトリに置かれますが、ほかの呼び出し側と同じように billing を import する必要があり、使えるのはエクスポートされた名前だけです。

example.gogo
package billing_test

import (
	"testing"

	"example.com/shop/billing"
)

func TestTotal(t *testing.T) {
	got := billing.Total([]int64{1000, 2000})
	if got != 3750 {
		t.Fatalf("Total = %d, want 3750", got)
	}
}

//go:build ignore を付けたジェネレータスクリプトのように、ビルド制約で除外されるファイルを別にすれば、1 つのディレクトリに 2 つのパッケージ名を共存させる方法は _test という接尾辞だけです。go test は 2 つのパッケージを別々にビルドします。外部テストは、呼び出し側と同じ使い方で API を検証します。標準ライブラリが、ドキュメントに表示される実行可能な Example 関数を書くのにも外部テストを使っています。多くの Go のコードベースでは両方を併用し、複雑な内部処理にはパッケージ内のテストを、公開された振る舞いには _test パッケージを使っています。

package 句の前には何を書けるのか?

書けるのはコメントと空行だけです。その位置に置くコメントのうち 2 種類は、ツールチェーンにとって意味を持ちます。

パッケージのドキュメントコメントは、空行を挟まずに package の直前に置きます。慣習では Package billing で始め、go doc や pkg.go.dev はこれをパッケージのドキュメントとして表示します。大きなパッケージでは、このコメントだけを書いた doc.go というファイルに置くことがよくあります。package main のコマンドでは、// Api serves the shop's HTTP API. のように、代わりにプログラムの説明を書きます。

ビルド制約は、そのファイルをコンパイルするかどうかを決めます。package 句より前に書き、その後に空行を入れる必要があります。

example.gogo
//go:build debug

package billing

import "log"

func init() {
	log.Println("billing: debug build")
}

このファイルは go build -tags debug のときだけコンパイルされます。watch_linux.go や asm_arm64.go のように OS やアーキテクチャの接尾辞が付いたファイルは、コメントがなくてもファイル名によって同じ扱いを受けます。

この例では init も使っています。パッケージは init 関数をいくつでも宣言でき、1 つのファイルに複数書くこともできます。Go はパッケージレベルの変数を初期化した後、そのパッケージを import しているものが動き出す前に init を実行します。import されたパッケージは、それを import するパッケージより先に完全に初期化されるので、main は常に最後に実行されます。init はドライバーの登録のような軽い初期設定だけに使いましょう。失敗する可能性のある処理は、エラーを返す関数に書くべきです。

LevelUpGo で学ぶ

LevelUpGo では、ブラウザ上で本物の Go コードを実行する演習を通じて Go を学べます。Packages & Organization コースでは、エクスポートされる名前、init とパッケージの状態、Go モジュール、プロジェクト構成を扱います。Code Organization では、大きくなっていくコードベースを、わかりやすい名前のパッケージに分割する方法を紹介します。Go を始めたばかりなら、まず Go Basics から取り組んでください。残り 24 個の予約語については、Go のキーワード一覧:全 25 個の予約語を解説をご覧ください。

よくある質問

Go のパッケージ名はディレクトリ名と違っていてもよいですか?

はい。コンパイラが読むのは package 句だけなので、httpapi-v2 というフォルダの中に package httpapi を置いても問題なくビルドできます。慣習では名前を import パスの最後の要素と一致させます。主な例外は、gopkg.in/yaml.v3(パッケージ yaml)や math/rand/v2(パッケージ rand)のようなバージョンの接尾辞です。

Go のプログラムにはなぜ package main が必要なのですか?

go コマンドが実行ファイルをビルドするのは main という名前のパッケージからだけで、ランタイムはそのパッケージの main 関数を呼び出してプログラムを開始します。それ以外のパッケージはすべてライブラリです。func main() のない package main は function main is undeclared in the main package で失敗します。

1 つの Go モジュールに package main を複数置けますか?

はい、ディレクトリごとに 1 つずつ置けます。API サーバーとマイグレーションツールを持つサービスには、通常 cmd/api/main.go と cmd/migrate/main.go があり、どちらも package main を宣言しています。それぞれ、go run ./cmd/api や go build ./cmd/migrate のようにディレクトリを指定してビルドまたは実行します。

Go のパッケージ名にアンダースコアや大文字を使えますか?

コンパイラはどちらも許可します。パッケージ名は _ 以外の有効な識別子なら何でもよいからです。しかし Go のスタイルでは、使わないことになっています。Go ブログの記事 Package names は、strconv や httputil のように、短く小文字で「under_scores や mixedCaps を含まない」名前を求めています。

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

はい。package は Go の 25 個の予約語の 1 つなので、変数名や関数名には使えません。パッケージを表す変数が必要なとき、Go のコードでは通常 pkg という名前を付けます。

出典

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

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

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