Dragon Arrow written by Tatsuya Nakaji, all rights reserved animated-dragon-image-0164

go言語 パッケージ

updated on 2020-12-15

Goのパッケージ


パッケージ

  • Goのプログラムはパッケージを組み合わせることで実現される
    • プログラムはmainパケージから開始し、別のパッケージをインポートする
      • パーケージをインポートすることで、様々な機能が使えるようになる
  • モジュール性、カプセル化、分離されたコンパイル、再利用をサポートするもの


パッケージの種類


mainパッケージ

  • main関数の存在するパッケージ
  • プログラムの起点(エントリポイント)となるパッケージ
  • 実行可能なGoのプログラムの場合には必ず存在する

標準パッケージ

  • Goが最初から用意しているパッケージ
  • 100以上のパッケージが存在する

サードパーティパッケージ

  • 第3者(自分も含む)が開発したパッケージ
  • インターネット上で公開されていることが多い
  • インストールすることで使える
  • ライブラリとも呼ばれる


パッケージのインポート


他のパッケージの機能を使う

  • インポートすることで使えるようになる
  • インポートはIDEやgoimportsなどのツールに任せる

パッケージをインポートしてできること

  • 別のパッケージで用意された機能を使用できる
    • 変数、関数、定数など


以下のように、標準パッケージとサードパーティパッケージは空行を挟んで分けることが多い

import (
       "context"
       "fmt"
      
       "github.com/tenntenn/greeting"
)


相対パスでのインポート

非推奨のやり方

import "./model"

絶対パスでのインポート

import "github.com/tenntenn/greeting"

単一行でのインポート

import "fmt"
import "string"

グループ化を使ったインポート

import (
    "fmt"
    "string"
)

ピリオドインポート

import (
    . "fmt"
    "string"
)

通常、「fmt.Println("hello world")」と書くところをドットをつけてインポートすると「Println("hello world")」と書くことができます。
つまり、ドット付きインポートはパッケージの関数を呼び出す際にパッケージ名を省略して書くことができます。

エイリアスインポート

import (
    f "fmt"
)

このような書き方でパッケージ名にエイリアスをつけることができます。
上の場合、「fmt.Println("hello world")」のかわりに「f.Println("hello world")」とエイリアスを用いて書くことができます。

ブランク(_)インポート

import (
    "database/sql"
    _ "github.com/ziutek/mymysql/godrv"
)

パッケージをインポートするだけで、パッケージの中の関数を直接使うわけではなく、このパッケージの中にあるinit関数をコールする。

インポート宣言は、インポート「する側」と「される側」の依存関係を宣言します。自分自身のパッケージをインポートすること、またはインポートしたパッケージ内でエクスポートされている識別子を一切参照しないことは誤った使い方です。インポートによる副作用(初期化)のためだけにパッケージをインポートするときは、パッケージ名としてブランク識別子を使う。



パッケージ名のエイリアス


別名をつける

  • インポートパスの左側に変えたい名前を書く
  • 同じパッケージ名のパッケージを使いたい場合に使う
  • インポートパスとパッケージ名が一致していない場合に用いる


import (
       "sync"

       mysync "github.com/tenntenn/sync" // syncパッケージと名前が衝突しているので、エイリアスを使う
       greeting "github.com/tenntenn/greeting/v2" // インポートパスとパッケージ名が一致していないので、エイリアスを使う
)
func main() {
    fmt.Println(greeting.Do())
}


パッケージ外へのエクスポート


 エクスポート

  • 先頭を大文字にした識別子がエクスポートされる
  • 他のパッケージから利用できるようになる


ライブラリ

  • main関数のないGoのプログラム
  • エクスポートされたものを使用する


以下の例で、Piという識別子はmathパッケージからエクスポートされている

package main


import (
    "fmt"
    "math"
)


func main() {
    fmt.Println(math.Pi)
}

(コンソール出力)

3.141592653589793


GOPATH


■ GOPATHとは?

  • Goのソースコードやビルドされたファイルが入るパスが設定される
  • インポートされるパッケージもここから検索される。


確認方法

$ go env GOPATH


$GOPATH

├── bin (ビルドされた実行可能ファイルが入る)

│   └── fuga

├── pkg

│   └── darwin_amd64 (ビルドされたパッケージが入る)

│       └── hoge.a

└── src

    ├── github.com

    │   ├── YourUsernameOfGihub (githubのユーザー名ディレクトリにgithubのプロジェクトを普通管理する)

    │   │   ├── github_cloned_repository (git cloneしたレポジトリ)

    │   ├── davecgh (go getコマンドで取得したサードパーティ)

    │   ├── pmezard (go getコマンドで取得したサードパーティ)

    │   └── stretchr (go getコマンドで取得したサードパーティ)

   ├── fuga

   │   └── main.go (実行可能なgoのコード)

   └── hoge

       └── hoge.go (ライブラリのgoのコード)


スコープ


スコープ

  • 識別子(変数名、関数名など)を参照できる範囲
    • 参照元によって所属するスコープが違う
    • 親子関係があり親のスコープの識別子は参照できる


ブロック

  • { と } に囲まれた一連の定義と宣言で、以下の種類がある
    • universe
      • ブロックの範囲は、全てのGoソース
    • package
      • ブロックの範囲は、パッケージの全てのGoソース
    • file
      • ブロックの範囲は、ファイル内の全てのGoソース
    • local
      • ブロックの範囲は、function body、if、for、switch、case、select内


エクスポートされた識別子

  • 以下の条件をどちらも満たす識別子を「エクスポートされた識別子」といい、他のパッケージからのアクセスを許可する
    • 識別子名の1文字目が大文字
    • packageブロック内で宣言されているか、フィールド名またはメソッド名である
  • 上記の条件を満たさない他の識別子はエクスポートされていないため、他のパッケージからアクセスできない


Goのスコープ

スコープは4種類

スコープ
universeブロック事前宣言された型

[ bool byte complex64 complex128 error float32 float64

int int8 int16 int32 int64 rune string

uint uint8 uint16 uint32 uint64 uintptr ]

定数

[ true false iota ]

ゼロ値
[ nil ]
関数

[ append cap close complex copy delete imag len 

make new panic print println real recover ]

packageブロックトップレベル関数外で宣言された定数、型、変数、関数(メソッド除く)
fileブロックインポートされたパッケージのパッケージ名
localブロックメソッドのレシーバ、関数の引数、戻り値の変数


init関数


init関数はパッケージの初期化時に呼び出される関数 (パッケージの初期化が先である)

  • mainパッケージに書くとmain関数より先に実行される
  • 複雑な初期化を行う場合に用いる
    • パッケージ変数への代入文だけでは表現できない場合
  • 1パッケージに複数用意しても良い
  • 1ファイルに複数用意しても良い
  • 実行順がシビアなものはinit関数には書かない
  • エラーハンドリングが必要な処理は書かない
  • init関数は明示的には呼び出せない


fmtパッケージが初期化された後に、initが呼び出されて、その後mainが呼び出されている

package main

import "fmt"

var msg = message()

func message() string {
    return "Hello"
}

func init() {
fmt.Print(msg)
}

func main() {
fmt.Println(", playground")
}

(コンソール出力)

Hello, playground



パッケージの初期化


依存パッケージの初期化

  • importしているパッケージリストを出す
  • 依存関係を解決して依存されてないパッケージから初期化していく


各パッケージの初期化

  • パッケージ変数を初期化する
  • init関数の実行を行う


ライブラリのバージョン管理


go getコマンド

Goのライブラリなどを取得するコマンド

依存するライブラリも一緒に取得してくれる

指定した場所からダウンロード&インストールしてくれる

一度取得したものは2度取得しない

-uオプションでダウンロードを強制する


$ go get github.com/tenntenn/greeting
$ ls $GOPATH/src/github.com/tenntenn/greeting
README.md          greeting.go


実際に使ってみる

package main

import (
    "fmt"
    "time"

    greeting "github.com/tenntenn/greeting/v2"
)


func main() {
        t := time.Now()
    fmt.Printf("現在時刻: %v あいさつ: %s\n", t, greeting.Do(t))
}

(コンソール出力)

現在時刻: 2020-12-06 16:31:38.257042 +0900 JST m=+0.000115566 あいさつ: こんにち


goDocの生成


godoc

  • ソースコード上に書かれたコメントをドキュメントとして扱う
  • @paramなどや@returnのような凝った記法はない
  • エクスポートされたものに書くことが基本


godoc.org

  • godoc.orgは自動でGoDocを生成するサービス
  • URLの後ろにインポートパスを書いてアクセスする


$ godoc -http=:6060 (ipは任意)

パッケージまでのパスが $GOPATH/src/sample 場合、

http://localhost:6060/pkg/sample でアクセス


modules (vgo)


Go 1.11よりModules(vgo)という仕組みが標準装備になっている


GO111MODULE

  • Go 1.11から設定出来るようになった環境変数のこと、on/off/autoの3種類の値を設定することが出来る。
  • これを設定することでModuleを使うのか、今まで通り$GOPATHを用いて依存関係を管理するのか設定することが出来る。

onの場合

  • modulesを使う、module-aware modeと呼ぶらしい。
  • $GOPATH配下じゃなくても、依存関係を解決出来るらしい。
  • $GOPATH/src にソースコードが置かれない($GOPATH/srcに置くためには、GO111MODULE=off go get ライブラリ という風にしなければならない)

offの場合

  • $GOPATHを使った依存解決を行う必要がある。

autoの場合

  • go.modが存在し、$GOPATH内であればmodulesを使うようになる。



$ go mod init # カレントディレクトリに新しいモジュールを初期化し、go.modが作成される


hello.goを作ってみる

package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Opt())
}


ビルドすると、自動でライブラリ "rsc.io/quote" とその依存ファイルをダウンロードしてビルドしてくれる! 

ビルドした際に、go.modにはファイルを実行するのに必要なモジュールが記述され、go.sumにはモジュールのバージョンが記述される。

ライブラリは$GOPATH/pkg/mod に保存される

go installすると$GOPATH/bin に保存される


$ go build
go: finding module for package rsc.io/quote
go: downloading rsc.io/quote v1.5.2
go: found rsc.io/quote in rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c


Go Modules ではプロジェクトを GOPATH の外に置けるが、ライブラリのキャッシュや go install のコピー先として GOPATH (デフォルトで ~/go) が使われる。


ライブラリのバージョンの決め方

一旦必要なバージョンが go.mod に記載されると、それ以降は go コマンドは勝手にライブラリのバージョンを上げない。これでバイナリ作成時の依存バージョンを特定出来る


モジュールのアップデート

$ go get -u # go.modファイルが更新される


モジュールの削除

go get で明示的にパッケージをインストールすると go.mod に使わないライブラリのカスが貯まるので、以下のコマンドで使うやつだけに整理する。

$ go mod tidy # 足りないモジュールを追加し、使用されていないモジュールを削除する