Go言語 – 8.パッケージ

2019-02-01Go言語

Go言語 パッケージ

今回は「パッケージ 」について説明します。
Go言語では、名前空間を分けるための仕組みとして、「パッケージ」を使用します。
モジュール性、カプセル化、分離されたコンパイル、再利用をサポートするものです。
プログラムを分割することで機能を分離し、複雑な状態にしないようにしていきましょう。

パッケージの宣言

package somepkg

var SomeVar int
var someVar2 int

func SomeFunc() {
    SomeVar = 10
    someVar2 = 5
}

func someFunc2() {
    SomeFunc()
}

上記のコードでは、1行目でパッケージ名somepkgを宣言しています。
これにより、変数SomeVarsomeVar2や関数SomeFuncsomeFunc2somepkgパッケージのメンバとなります。
同パッケージ内のメンバは、無制限に参照することができます。

他パッケージメンバの参照

package otherpkg

func OtherFunc() {
    SomeFunc() // エラー
    SomeVar = 5 // これもエラー
}

上記のコードでは、somepkgとは別のパッケージotherpkgを宣言しています。
otherpkgからsomepkgのメンバへアクセスしようとしていますが、これはエラーになります。
他のパッケージのメンバにアクセスするには、import文を使用します。
import文によって読み込んだパッケージのメンバへのアクセスは、パッケージ名.メンバ名という形で行います。

package otherpkg

import "somepkg"

func OtherFunc() {
    somepkg.SomeFunc()
    somepkg.SomeVar = 5
}

パッケージ別名

package otherpkg

import some "somepkg"

func OtherFunc() {
    some.SomeFunc()
    some.SomeVar = 5
}

パッケージのimport時に、パッケージに別名を付けることができます。
例えば上記のコードではパッケージsomepkgにsomeという別名を付けて使用しています。
パッケージ別名は長いパッケージ名を扱う場合や、同名の別パッケージを複数使用する場合に役立ちます。
なお、別名として.を指定したパッケージは、メンバへのアクセス時にパッケージ名の指定を省略することができます。

package otherpkg

import . "somepkg"

func OtherFunc() {
   SomeFunc()
   SomeVar = 5
}

折角分けた名前空間を混ぜてしまうことになるので原則的に推奨されませんが、
別パッケージに分けざるを得なかったテストコードから製品コードを参照する場合など、
ごく稀に活用できる場面があります。

メンバの可視性

パッケージのメンバのうち、名前の頭文字が英字大文字で始まるものはimport文を使用することで他パッケージからの参照が可能です。
このようなメンバをパブリックメンバと呼びます。

package otherpkg

import "somepkg"

func OtherFunc() {
    somepkg.SomeFunc()
    somepkg.SomeVar = 5
}

一方、名前の頭文字が小文字のメンバはimport文を使用しても他パッケージから参照することができません。
このようなメンバをプライベートメンバと呼びます。

package otherpkg

import "somepkg"

func OtherFunc() {
somepkg.someFunc2() // エラー
somepkg.someVar2 = 5 // エラー
}

パッケージに関する禁則事項

循環参照

二つのパッケージがお互いにもう片方をimportをするとコンパイルエラーになります。
例えば、以下の2つのパッケージは循環参照によりエラーとなります。

package test1

import "fmt"
import "test2"

var SomeVar = "test1"

func SomeFunc() {
    fmt.Println(test2.SomeVar2)
}
package test2

import "fmt"
import "test1"

var SomeVar2 = "test2"

func SomeFunc2() {
    fmt.Println(test1.SomeVar)
}

複数パッケージの同フォルダへの配置

1つのフォルダ直下に、互いに異なるパッケージが宣言された複数のコードを置くことはできません。
つまり、以下のような構造はエラーになります。

$GOPATH/
 ┗ src/
   ┗ test/
    ┣ test.go(パッケージ名:test)
    ┗ test2.go(パッケージ名:test2)

逆に、パッケージが同じソースコードであれば同ディレクトリにいくつでも配置が可能です。

例外

実は、同フォルダに複数パッケージを配置できる例外的なケースが一つだけあります。

$GOPATH/
┗src/
  ┗somepkg/
    ┣some.go(パッケージ名:somepkg)
    ┗some_test.go(パッケージ名:somepkg_test)

ファイル名の(拡張子を除いた)末尾が_testで終わるコードはテストコードとして扱われる(詳しくは別章で説明します)のですが、
テストコードについてはテスト対象のパッケージ名_testというパッケージ名を使用することが許されています。

殆どの場合テストコードはテスト対象のパッケージに含めるため使うことは稀ですが、
先述の循環参照を回避するためにこの仕様を利用するケースがあります。