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

go言語 パッケージ

updated on 2020-11-07

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関数の実行を行う