まくまくGo言語ノート
パッケージの作成とインポート (import)
2022-04-10
Go 言語でパッケージを作る方法と、他のパッケージを参照する色々な方法を紹介します。

パッケージとモジュール

パッケージ

Go 言語のパッケージは、1 つのディレクトリ内にまとめられたソースコードの集まりです。 下記のディレクトリ構成では、mymodule というモジュール(後述)に、pkg1pkg2 という名前の 2 つのパッケージが含まれています。

- mymodule
  - pkg1/
    - auth.go (package pkg1)
    - user.go (package pkg1)
  - pkg2/
    - hoge.go (package pkg2)

パッケージ内のファイルの分割粒度は自由で、あるコードがパッケージ内のどの Go ファイルに記述されているかで違いはありません。 パッケージのインポート時にはパッケージ名しか指定しないので、パッケージの利用者は、パッケージを構成する各々の Go ファイルの存在を意識する必要はありません。 パッケージの実装者は、パッケージ内のファイルを都合のいいように分割できます。

Go 言語のソースコードは、必ず 1 つのパッケージに所属している必要があり、ソースコードの先頭の package ディレクティブでパッケージ名(=親ディレクトリ名)を指定します。

package pkg1

特に、プログラムのエントリポイントとなる main 関数は、main パッケージに存在している必要があり、典型的にはモジュールルートを main パッケージとして扱います。

package main

function main() {
	// ...
}

モジュール

Go 1.11 以降は モジュール という考え方が導入され、Go 1.10 以前の GOPATH によるコードの一元管理 からの移行が進みました。 簡単に言うと、プロジェクト単位でディレクトリを自由な場所に置けるようになりました(以前はすべてのプロジェクトを $GOPATH 以下に置かなければいけませんでした)。 モジュールは単一あるいは複数のパッケージを集めたもので、GitHub などで管理するときののリリース単位として用いられます。

モジュール ◇── パッケージ

モジュールのルートディレクトリには go.modgo.sum というファイルが置かれ、依存するパッケージのバージョンやハッシュ情報などが管理されます。 公開されるモジュール名は世界で一意になっている必要があり、一般的に GitHub のリポジトリで管理されるため、次のような名前が使われます。

github.com/<username>/<modulename>

例えば、github.com/maku77/go-modules として公開されているモジュールの、pkg1 パッケージを利用したい場合、次のようにインポートできます。

import "github.com/maku77/go-modules/pkg1"

上記のように、モジュール内の単一のパッケージをインポートする場合でも、モジュール全体がダウンロードされるため、初回のビルドには少し時間がかかります。

インポートの使い方

基本

Go 言語のプログラムの中から、他のパッケージをインポートするには import を使用します。 GitHub で公開されているパッケージを直接インポートすることもできます。

import "fmt"
import "os"
import "strings"
import "github.com/ユーザー名/リポジトリ名/パッケージ名"

括弧を使って次の様にまとめることもできます。

import (
	"fmt"
	"os"
	"strings"
	import "github.com/ユーザー名/リポジトリ名/パッケージ名"
)

パッケージで定義されている関数を参照するときは、パッケージ名.関数名 という形で呼び出します。 大文字で始まる関数のみ参照できます。

package main

import "fmt"
import "github.com/google/go-cmp/cmp"

func main() {
	fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

オプション

パッケージをインポートするときに、パッケージ名の前にオプションを指定することで、別名を付けたり、パッケージ名を省略してアクセスできるようになります。

package main

import (
	. "fmt"                             // パッケージ名を省略して呼び出せるようにする
	sample "github.com/maku77/gosample" // 別名を付けて参照
	_ "math/rand"                       // 参照していなくてもコンパイルエラーにしない
)

func main() {
	Println("Good morning")
	sample.Hello("Maku")
}

パッケージ名の前に . オプションを付けると、パッケージ名を省略してアクセスできるようになります。 上記の例では、fmt.Println の呼び出しを、Println と記述できるようにしています。

2 つ目の例では、gosample パッケージに sample という別名を付けてアクセスできるように指定しています。 Go 言語では、パッケージ名を階層構造で表現しない(潔い!)ので、パッケージ名のコンフリクトが発生しやすくなっています。 同じ名前のパッケージをインポートするときは、この別名の仕組みを利用して解決しましょう。

同一パッケージ内の関数を参照する

同じパッケージ内(つまり、同じディレクトリ内)の Go ファイルで定義されている関数は、互いに参照することができます。 下記は、プログラムのエントリポイントとなる main.go ファイルです。

main.go

package main

func main(){
	hello("Maku")
}

hello 関数は、同じパッケージ(同じディレクトリ内)の別のファイルで定義しています。

greet.go

package main

import "fmt"

func hello(name string) {
	fmt.Printf("Hello, %s\n", name)
}

実行するときは、これらを一緒にビルドする必要があります。

$ go run *.go
Hello, Maku

ビルドして、実行ファイル myapp を作るときも同様です。

$ go build -o myapp *.go
$ ./myapp
Hello, Maku

go.mod ファイルを生成してモジュール対応モードにしておけば、go build は引数なしで実行できます。

$ go mod init myapp
$ go build
$ ./myapp
Hello, Maku

別のパッケージの関数を参照する

ある関数を別のパッケージから参照できるようにするには、関数名を大文字で始める必要があります(例: Hello)。 ここでは、次のようなディレクトリ構成の myapp モジュールを作って、main.go(main パッケージ)から mymath/mymath.go(mymath パッケージ)で定義した関数を参照したいとします。

myapp/
  go.mod
  main.go (main パッケージ)
  mymath/ (mymath パッケージ)
    mymath.go

まず、モジュールのルートディレクトリ (myapp) で、go mod init コマンドを実行して、go.mod ファイルを生成します。 ここではローカルだけで使うことを前提に、とりあえずモジュールパス名を com.example/myapp としますが、GitHub での公開を想定しているのであれば、github.com/<user>/<repo> のように世界で一意となる名前を付けるべきです。

$ cd myapp
$ go mod init com.example/myapp

mymath パッケージでは、簡単な足し算を行う Add 関数を提供することにします。 パッケージ外部から参照できるようにするには、関数名を大文字で始める必要があります。

mymath/mymath.go

package mymath

func Add(a, b int) int {
	return a + b
}

この mymath.Add 関数を main 関数から参照するには次のようにします。

main.go

package main

import "fmt"
import "com.example/myapp/mymath"

func main() {
	fmt.Println(mymath.Add(100, 200))
}

main パッケージ(の main 関数)は次のように実行できます。

$ go run .
300

ビルドして実行ファイル myapp を生成することもできます。

$ go build
$ ./myapp
300

Go 言語のパッケージは、"./mymath" のような 相対パスではインポートできない ことに注意してください。 必ず "com.example/myapp/mymath" のようにモジュール名を含むパッケージパス全体(絶対パス)を指定する必要があります。 昔は同一モジュール内のパッケージであれば相対パスでインポートできたのですが、現在は外部モジュールのパッケージと同様に絶対パスによる指定に統一されています。

2022-04-10