Goを真面目に勉強する〜4.パッケージとスコープ〜

#はじめに
Goをはじめて1年半。アウトプットが進まない私が、専門家の@tenntennさんから受けたマンツーマンレッスンの内容をまとめて、Goのスキルアップを目指します。Goの基礎から丁寧に学んでいきます。
記事のまとめは以下の通りで順次作成していきます。
今回は「4.パッケージとスコープ」になります。

## シリーズの一覧
1. [Goについて知っておく事](https://qiita.com/bluemon0919/items/dabda22b50df44999790)
2. [基本構文](https://qiita.com/bluemon0919/items/2d0e96d330e6c6d96b8d)
3. [関数と型](https://qiita.com/bluemon0919/items/000de655f4ab27ea43ae)
4. パッケージとスコープ（今回）

## 本記事の内容
今回学ぶ内容は以下の通りです。

- [パッケージ](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8)
- [ライブラリ](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA)
- [スコープ](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97)

#パッケージ
パッケージは関数や変数、定数、型を意味のある単位でまとめたもので、Goのプログラムはパッケージを組み合わせることで実現されます。

Goのプログラムは`main`関数のある`main`パッケージを起点に実行されます。`main`パッケージから別のパッケージをインポートすることで、様々な機能が利用できるようになります。

###パッケージの種類

| 種類 | 説明 |
|-----------------|------------------|
|mainパッケージ|main関数が存在し、プログラムの起点（エントリポイント）となるパッケージ|
|標準パッケージ|Goが最初から用意しているパッケージ|
|準標準パッケージ|golang.org/xで提供されるパッケージ|
|サードパーティパッケージ|第3者（自分も含む）が開発したパッケージで、多くがインターネット上で公開されている。ライブラリとも呼ばれる。|

##パッケージを使う
###パッケージのインポート
パッケージをインポートすることで、他のパッケージの機能を利用することができます。
次の例の場合、`main`パッケージ内で標準パッケージの`fmt`パッケージをインポートすることで、`fmt`パッケージが提供する`Println`関数を利用しています。

```go

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello world!")
}
```

複数のパッケージをインポートする際には次のように羅列して記述します。

```go

import (
	"fmt"
	"log"
	"net/http"
)
```

ビルドにはライブラリを取得しておく必要があります。取得の仕方は[ライブラリを取得する](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)を参照。

###パッケージのエイリアス
パッケージ名には別名（エイリアス）をつけることができます。エイリアスは同じパッケージ名のパッケージを使いたい場合や、インポートパスとパッケージ名が一致しない場合に利用します。

- `sync`パッケージとサードパーティパッケージのパッケージ名が衝突しているため、サードパーティパッケージに`mysync`という別名をつける

```go

import (
	"sync"
	
	mysync "github.com/tenntenn/sync" // syncパッケージと名前が衝突している
	greeting "github.com/tenntenn/greeting/v2" // インポートパスとパッケージ名が一致しない
)
```

##パッケージを作る
自作のパッケージを作って`main`パッケージにインポートすることができます。
次の例は、`int`型の数値データを16進数の文字列に変換する機能を自作パッケージmyhexとしたものです。
パッケージ名は[`"package" PackageName`](https://golang.org/ref/spec#Package_clause)で定義します。

```myhex/myhex.go

package myhex // 自作パッケージmyhex

import "fmt"

type Hex int

func (h Hex) String() string {
	return fmt.Sprintf("%x", int(h))
}
```

myhexパッケージをインポートすることで、提供する機能を利用することができます。

```main.go

package main

import (
	"fmt"
	"myhex" // 自作パッケージmyhexをインポートする
)

func main() {
	var hex myhex.Hex = 100 // myhexパッケージのユーザー定義型Hexで変数hexを宣言する
	fmt.Println(hex) // myhexパッケージのStringメソッドが実行される
}
```

###パッケージの実装を複数のファイルで行う
パッケージは複数ファイルで実装して結合することができます。ただし、結合できるのは同じフォルダ内にある場合に限ります。（[パッケージを配置する](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B)を参照）

```myhex/string.go

package myhex // 自作パッケージmyhex
...
```

```myhex/rune.go

package myhex // 自作パッケージmyhex
...
```

###パッケージ外へのエクスポート
パッケージで定義した関数や変数、定数、型はエクスポートすることで外部から利用できるようになります。Goでは先頭を大文字にした識別子がエクスポートされます。

```go

type Hoge int // exported
type hoge int // unexported

var Hoge string // exported
var hoge string // unexported

const Hoge int = 100 // exported
const hoge int = 200 // unexported

func Hoge() {} // exported
func hoge() {} // unexported
```

###パッケージを配置する
`hoge`パッケージを作成した場合の例です。
GOPATH以下の任意の場所に、パッケージ名のフォルダで配置します。
![alt](https://docs.google.com/drawings/d/e/2PACX-1vQFXrZQ407XuUB7BpMAi-6jhVB7-8tYM0zS-_qyMs11KaP2GrQKnYpWkqX3JeQOzz-PwIwIwD14rUVb/pub?w=851&h=472)


##パッケージの初期化
パッケージの初期化は以下の順に行われます。

**依存パッケージの初期化順**
1. インポートしているパッケージリストを作成
2. 依存関係を解決し、依存されていないパッケージから初期化する
2.1. パッケージ変数を初期化する
2.2. `init`関数を実行する

###init関数
- パッケージの初期化を行う関数で、初期化時に実行される。
- プログラム内から明示的に呼ぶことはできない。
- `init`関数は同一のパッケージ内やファイル内に複数宣言することができる。ただし、実行順はGoの実装によるため、実行順がシビアなものは`init`関数には書いてはいけない。
- パッケージ内の`init`関数が同時に実行されることは無い。
- 複数のパッケージからインポートされている場合でも、`init`関数は一度だけ呼ばれる。

#ライブラリ
##ライブラリを取得する(go get)

`go get`コマンドを利用してライブラリを取得します。

- Goのライブラリなどを取得するコマンド
- 依存するライブラリも一緒に取得してくれる
- 指定した場所からダウンロード&インストールしてくれる
- 一度取得したものは2度取得しない

```
go get github.com/tenntenn/greeting
```
##ライブラリのバージョンを指定する(Go Modules)
`go get`コマンドはライブラリのlatestバージョンを取得するため、特定のバージョンのライブラリを使いたい場合は`go get`コマンドでは管理できません。そこで、登場するのがGo Modules(vgo)です。

Go Modulesを使うためには環境変数の`GO111MODULE`を`on`にします。
パッケージをインポートした上で、`go mod init`コマンドを実行すると`go.mod`と`go.sum`ファイルが生成されます。
（依存モジュールがない場合は`go.sum`は生成されません）

| 種類 | 説明 |
|-----------------|------------------|
|go.mod|依存モジュールを管理|
|go.sum|依存モジュールのチェックサムを管理|

`go.mod`ファイル内で依存モジュールは以下の形式で管理します。
`require <ライブラリ> <バージョン>`
バージョンは[semver](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%8B%E3%83%B3%E3%82%B0semver)で記述して、特定のコミットも指定することができます。

```go.mod
module hello

go 1.13

require github.com/tenntenn/greeting/v2 v2.0.0
```

```go.sum
github.com/tenntenn/greeting v0.0.0-20190203153616-a128ec76efc4 h1:1lfizcROSeZN+ejPMSYmpEiubZ724OA3WzhlIvfjYRA=
github.com/tenntenn/greeting v0.0.0-20190203153616-a128ec76efc4/go.mod h1:E3Ht/04nVnsgPJqBdZYg2BmvqQJmRPxhIv6qX4RyE9Y=
github.com/tenntenn/greeting/v2 v2.0.0 h1:9vHheZ4F7JS/u4fqBfKIHtiUI38t98W8iDRFFXeaccA=
github.com/tenntenn/greeting/v2 v2.0.0/go.mod h1:EkHY3zj6AR1MtKVmdhvooxb+zIUEihioi7gG93rsX5I=
```


####セマンティックバージョニング(semver)
- バージョンの付け方のルール
- semverと略される
- v0.0.2やv.11.1などと表記する

**バージョンのあげ方**

- 互換が崩れる場合はメジャーバージョン（例：v1.2.3 → v2.0.0）
- 機能追加の場合はマイナーバージョン（例：v1.2.3 → v1.3.0）
- バグ修正の場合はパッチバージョン（例：v1.2.3 → v1.2.4）

###Go Modulesのコマンド
| 種類 | 説明 |
|-----------------|------------------|
|go mod init|go.mod,go.sumファイルを生成する|
|go mod tidy|使用していないパッケージをgo.modから削除する。<br>必要なパッケージをgo.modに追加する。|
|go mod why|指定したパッケージがなぜ必要になったか表示する|


#スコープ
スコープは識別子（変数名、関数名など）を参照できる範囲のことで、参照元によって所属するスコープが違います。親子関係があり親のスコープの識別子は参照できます。


| 種類 | 説明 |
|-----------------|------------------|
|ブロックスコープ|ブロックごとのスコープ。関数やif、forなどのブロック単位。ブロックは{}で囲まれた範囲。|
|ファイルスコープ|ファイルごとのスコープ。ファイル内でインポートしたパッケージを保持する。パッケージ以外は対象としていない。|
|パッケージスコープ|パッケージごとのスコープ。大文字から始まる識別子は他のパッケージからも参照できる。（[パッケージ外へのエクスポート](https://qiita.com/drafts/4127ecbcadadd9be2b1c#%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E5%A4%96%E3%81%B8%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88)を参照）|
|ユニバーススコープ|組み込み型や組み込み関数を保持するスコープ。プログラム実行時からずっとあるスコープ。他のスコープの一番ルートとなる。|

### ブロックスコープ

```go
func f() {
	n := 100
	println(n)
	if true {
		n := 200 // 100が入った変数とは別のもの
		println(n)
	}
}

func g() {
	n := 300 // 100,200が入った変数とは別のもの
	println(n)
}
```

### ファイルスコープ

```go
// fmtがファイルスコープになる
import "fmt"
```

### パッケージスコープ

```go
package myhex

import "fmt"

type Hex int // パッケージスコープの型

func (h Hex) String() string { // パッケージスコープの関数
    return fmt.Sprintf("%x", int(h))
}
```

### ユニバーススコープ

```go
false := true // 定数falseはユニバーススコープ
if false {
	fmt.Println(false) // trueが表示される
}

type int string // int型はユニバーススコープ
var n int
fmt.Printf("%q\n", n) // ""が表示される
```
##パッケージ変数
関数間の変数の共有は、パッケージスコープで変数を宣言すれば可能です。

```go
var msg string = "hello"
func f() { println(msg) }
func main() {
	f()
	msg = "hi, gophers"
	f()
}
```