モジュールとgo.mod
モジュールは、ルートにgo.modを置いたソースの木です。依存の境界であり、go.modのmodule行がモジュールパスになります。そのモジュール内のパッケージのimportパスは、このモジュールパスを先頭にします。
mkdir demo
cd demo
go mod init example.com/demomodule example.com/demo
go 1.26go行は、このモジュールが想定する言語の版です。サブディレクトリごとにgo mod initする必要はありません。同じgo.modの下にあるパッケージは、すべてこのモジュールに属します。
外部パッケージをimportすると、必要なモジュール版がgo.modに記録されます。依存の整理にはgo mod tidyが使われます。
パッケージ
パッケージはコンパイルの単位であり、識別子の名前空間でもあります。通常、1つのディレクトリが1つのパッケージです。同じディレクトリの.goファイルは、同じpackage句を書き、まとめてコンパイルされます。あるパッケージの識別子を、別パッケージから自由に全部使えるわけではありません。名前の先頭が大文字か小文字かで、外から見えるかどうかが決まります。
// greeter/greet.go
package greeter
func Hello(name string) string {
return "hello, " + name
}// main.go
package main
import (
"fmt"
"example.com/demo/greeter"
)
func main() {
fmt.Println(greeter.Hello("Go"))
}greeterディレクトリのimportパスは、モジュールパスに相対ディレクトリを足したexample.com/demo/greeterです。
importパスとパッケージ名
importパスは、import "..."に書く文字列です。モジュールパスにディレクトリを足したもので、「どのフォルダのコードを取り込むか」を指定します。
パッケージ名は、取り込んだあとにコード上で使う名前です。そのディレクトリの.goファイル先頭のpackage句で決まります。importパスの末尾と同じとは限りません。
揃っている例です。ディレクトリgreeter/のソースがpackage greeterなら、呼び出しもgreeterで書きます。
// greeter/greet.go
package greeter
func Hello(name string) string {
return "hello, " + name
}import "example.com/demo/greeter" // importパス(場所)
greeter.Hello("Go") // パッケージ名(package句)package句だけ違うと、importはできても呼び出し名が変わります。たとえばpackage greetならgreet.Helloであり、greeter.Helloは見つかりません。
// greeter/greet.goがpackage greetのとき
import "example.com/demo/greeter"
greet.Hello("Go") // 使えるのはpackage句の名前
// greeter.Hello("Go") // コンパイルエラーパッケージ名は短い小文字の単語1つが望ましいです(http、json、bufioなど)。呼び出し側は常にパッケージ名.識別子と書くため、型名や関数名にパッケージ名を重ねると冗長になります。たとえばバッファ付きの読み取り型はbufio.Readerであり、bufio.BufReaderのようにはしません。
公開と非公開
識別子の公開範囲は、名前の先頭文字で決まります。大文字で始まる名前は他パッケージから参照でき、小文字で始まる名前は同じパッケージ内だけです。
package greeter
func Hello() string { // 他パッケージから呼べる
return hello()
}
func hello() string { // 同じパッケージ内だけ
return "hello"
}import "example.com/demo/greeter"
greeter.Hello()
// greeter.hello() // コンパイルエラー: 小文字は他パッケージから見えないimport宣言
ソースはpackage句のあと、import、その後に宣言が続きます。
import "fmt"
import (
"errors"
myio "io"
_ "image/png"
)別名を付けると、パッケージ名の衝突を避けられます。myio "io"ならmyio.Readerのように使います。
識別子を使わず、パッケージの初期化だけ実行したいときは_ "image/png"のようにブランクimportします。デコーダの登録など、initの副作用が目的のときに使います。
import . "fmt"は、パッケージ名なしで識別子を取り込む書き方です。通常ならfmt.Printlnと書くところを、Printlnだけで呼べます。
import . "fmt"
func main() {
Println("hello") // fmt.Println("hello")と同じ
}Printlnが自パッケージの関数なのかfmtのものなのか、呼び出し箇所だけでは分かりにくくなります。そのため通常のコードでは使いません。
internalパッケージ
パスにinternalを含むパッケージは、そのinternalの親ディレクトリ以下からだけimportできます。モジュール全体から見えるわけではなく、ディレクトリの位置で決まります。
たとえばexample.com/demo/internal/storeは、example.com/demo/...配下からはimportできますが、別モジュールやexample.com/demoの外にあたるパスからはimportできません。公開したくない実装を内側に閉じるための仕組みです。
ドキュメントコメント
公開する宣言の直前に書いたコメントが、ドキュメントとして扱われます。
// ParseConfigは設定ファイルを読み込む。
func ParseConfig(path string) (*Config, error) {
// ...
}フォーマット
Goのソースは、手で細かく揃えるのではなくgofmt(パッケージ単位ならgo fmt)で機械的に整形するのが慣習です。インデントはタブ、空白の入れ方や並びも標準形に揃います。スタイルの好みで議論せず、ツールの出力に合わせます。
ファイルをその場で書き換える例です。
gofmt -w main.goパッケージ(ディレクトリ)単位で揃えるときはgo fmtを使います。結果のスタイルはgofmtと同じです。
go fmt ./...gofmtは標準入力の断片も整形できます。go fmtはパスで指定したパッケージ配下の.goファイルをまとめて扱います。
テスト
テストは、対象と同じディレクトリに_test.goで終わるファイルを置きます。書き方は2通りあり、小文字の識別子を直接試すかどうかで選びます。
同じpackage greeterで書くと、小文字の識別子も含めパッケージ内部が見えます。非公開の補助関数や内部状態まで検証したいときに使います。importは不要で、同じパッケージの関数をそのまま呼べます。
// greeter/greet_test.go
package greeter
import "testing"
func TestHello(t *testing.T) {
if Hello("Go") != "hello, Go" {
t.Fatal("unexpected greeting")
}
}package greeter_testで書くと、他パッケージからの利用と同じです。importが必要で、大文字の公開APIだけを試せます。利用側と同じ見え方で公開面を確認したいときに使います。非公開の識別子はここからは触れません。
// greeter/greet_ext_test.go
package greeter_test
import (
"testing"
"example.com/demo/greeter"
)
func TestHello(t *testing.T) {
if greeter.Hello("Go") != "hello, Go" {
t.Fatal("unexpected greeting")
}
// greeter.hello() // コンパイルエラー: 他パッケージからは見えない
}どちらもよく使われます。非公開まで含めて試すなら同じパッケージ、公開APIだけを外から試すならgreeter_test、という分け方になります。1つのディレクトリに両方を置くこともできます。
go getで追加し、コードから使う
go getは、指定したモジュールを現在のモジュールの依存として足し、ソースをモジュールキャッシュへ取得します。モジュールキャッシュは、ダウンロードしたモジュールのソースをマシン上に蓄える場所です。既定では$GOPATH/pkg/modにあり、プロジェクトをまたいで再利用されます。プロジェクト直下にライブラリ用のディレクトリは増えません。依存の記録は、プロジェクト直下のgo.modに残ります。
go get rsc.io/quotego getだけでは、そのパッケージの関数はまだ呼べません。ソースにimportを書き、識別子を使います。
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Hello())
}go run .fmtのような標準ライブラリはgo getの対象ではありません。Goのインストールに含まれており、go.modのrequireにも現れません。
go.modとgo.sum
依存を足すと、go.modにrequire行が増えます。使うモジュールとバージョンの記録です。ここに書くバージョンは「このバージョンに固定」ではなく「このバージョン以上」です。自分のgo.modにexample.com/lib v1.2.0とあっても、使っている別モジュールのgo.modに同じモジュールがv1.4.0とあれば、ビルドではv1.4.0が使われます。
module example.com/demo
go 1.26
require rsc.io/quote v1.5.2あわせてgo.sumができます(または更新されます)。ここには、取得したモジュールバージョンの内容のチェックサムが並びます。次回以降のダウンロードで、同じバージョンの中身が変わっていないかを確かめます。
go.modは「何をどのバージョン以上で使うか」、go.sumは「そのバージョンの中身の指紋」です。役割が違うため、両方をソースと同じバージョン管理に含めます。
requireの行末に// indirectと付くことがあります。メインモジュールのパッケージから直接importされていないモジュールの印です。たとえばrsc.io/quoteを足すと、quoteが使うrsc.io/samplerなどがこの印付きで並ぶことがあります。go getした時点でまだimportしていなければ、そのモジュール自身にも同じ印が付きます。自分のコードへimportを足して直接使うようになれば、この印は外れます。手で付け外しする必要はなく、go getやgo mod tidyが更新します。
go mod tidyと依存のやめ方
もう一つの正規の足し方は、先にソースへimportを書き、あとから依存を揃えることです。
import "rsc.io/quote"go mod tidygo mod tidyは、モジュール内のソースがimportしている集合に合わせて、足りないモジュールをgo.modへ足し、どのパッケージからも使われないモジュールを落とします。go.sumも同様に揃えます。
依存をやめるときも、まずソースからそのimportと使用箇所を消し、もう一度go mod tidyを実行します。参照が無ければ、対応するrequireはgo.modから外れます。
バージョンの指定
バージョンを明示するときは、モジュールパスのあとに@とバージョンを付けます。
go get rsc.io/[email protected]
go get rsc.io/quote@latest@latestは、利用できるなかで最も高いリリースバージョンを選ぶ指定です。リリースバージョンがなければプレリリース、タグがなければリポジトリ既定ブランチ先端のバージョンになります。バージョンを省略したgo get rsc.io/quoteも、取得時点で選ばれた具体的なバージョンがrequireに書かれます。go.modのrequireにlatestという文字列を手で書いてはいけません。バージョンを変えるときはgo getを使います。
メジャーバージョンとimportパス
メジャーバージョンが2以上のモジュールでは、モジュールパスの末尾に/vNが付きます。go getにも、importに書く文字列にも、そのパスを使います。
go get rsc.io/quote/v3@latestimport "rsc.io/quote/v3" // v3のモジュールパス
quote.HelloV3()import "rsc.io/quote"はv1のモジュールパスです。これではv3のパッケージは使えません。
go getとgo install
現在のモジュールの依存を変えるのがgo getです。揃えるのがgo mod tidyです。
go get rsc.io/quote一方、バージョン付きで使うgo installは、コマンドを自分の環境へ入れる操作です。実行ファイルは$GOBINがあればそこへ、なければ$GOPATH/binに入り、パスが通っていればその場で呼べます。
go install golang.org/x/tools/cmd/stringer@latest
stringer -helpこのプロジェクトのgo.modのrequireは増えません。依存を足したいのにgo installだけ実行すると、プロジェクト側の記録は変わらないままになります。