#はじめに
Goをはじめて1年半。アウトプットが進まない私が、専門家の@tenntennさんから受けたマンツーマンレッスンの内容をまとめて、Goのスキルアップを目指します。Goの基礎から丁寧に学んでいきます。
記事のまとめは以下の通りで順次作成していきます。
今回は「4.パッケージとスコープ」になります。
シリーズの一覧
- Goについて知っておく事
- 基本構文
- 関数と型
- パッケージとスコープ(今回)
本記事の内容
今回学ぶ内容は以下の通りです。
#パッケージ
パッケージは関数や変数、定数、型を意味のある単位でまとめたもので、Goのプログラムはパッケージを組み合わせることで実現されます。
Goのプログラムはmain
関数のあるmain
パッケージを起点に実行されます。main
パッケージから別のパッケージをインポートすることで、様々な機能が利用できるようになります。
###パッケージの種類
種類 | 説明 |
---|---|
mainパッケージ | main関数が存在し、プログラムの起点(エントリポイント)となるパッケージ |
標準パッケージ | Goが最初から用意しているパッケージ |
準標準パッケージ | golang.org/xで提供されるパッケージ |
サードパーティパッケージ | 第3者(自分も含む)が開発したパッケージで、多くがインターネット上で公開されている。ライブラリとも呼ばれる。 |
##パッケージを使う
###パッケージのインポート
パッケージをインポートすることで、他のパッケージの機能を利用することができます。
次の例の場合、main
パッケージ内で標準パッケージのfmt
パッケージをインポートすることで、fmt
パッケージが提供するPrintln
関数を利用しています。
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world!")
}
複数のパッケージをインポートする際には次のように羅列して記述します。
import (
"fmt"
"log"
"net/http"
)
ビルドにはライブラリを取得しておく必要があります。取得の仕方はライブラリを取得するを参照。
###パッケージのエイリアス
パッケージ名には別名(エイリアス)をつけることができます。エイリアスは同じパッケージ名のパッケージを使いたい場合や、インポートパスとパッケージ名が一致しない場合に利用します。
-
sync
パッケージとサードパーティパッケージのパッケージ名が衝突しているため、サードパーティパッケージにmysync
という別名をつける
import (
"sync"
mysync "github.com/tenntenn/sync" // syncパッケージと名前が衝突している
greeting "github.com/tenntenn/greeting/v2" // インポートパスとパッケージ名が一致しない
)
##パッケージを作る
自作のパッケージを作ってmain
パッケージにインポートすることができます。
次の例は、int
型の数値データを16進数の文字列に変換する機能を自作パッケージmyhexとしたものです。
パッケージ名は"package" PackageName
で定義します。
package myhex // 自作パッケージmyhex
import "fmt"
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
myhexパッケージをインポートすることで、提供する機能を利用することができます。
package main
import (
"fmt"
"myhex" // 自作パッケージmyhexをインポートする
)
func main() {
var hex myhex.Hex = 100 // myhexパッケージのユーザー定義型Hexで変数hexを宣言する
fmt.Println(hex) // myhexパッケージのStringメソッドが実行される
}
###パッケージの実装を複数のファイルで行う
パッケージは複数ファイルで実装して結合することができます。ただし、結合できるのは同じフォルダ内にある場合に限ります。(パッケージを配置するを参照)
package myhex // 自作パッケージmyhex
...
package myhex // 自作パッケージmyhex
...
###パッケージ外へのエクスポート
パッケージで定義した関数や変数、定数、型はエクスポートすることで外部から利用できるようになります。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以下の任意の場所に、パッケージ名のフォルダで配置します。
##パッケージの初期化
パッケージの初期化は以下の順に行われます。
依存パッケージの初期化順
- インポートしているパッケージリストを作成
- 依存関係を解決し、依存されていないパッケージから初期化する
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で記述して、特定のコミットも指定することができます。
module hello
go 1.13
require github.com/tenntenn/greeting/v2 v2.0.0
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から削除する。 必要なパッケージをgo.modに追加する。 |
go mod why | 指定したパッケージがなぜ必要になったか表示する |
#スコープ
スコープは識別子(変数名、関数名など)を参照できる範囲のことで、参照元によって所属するスコープが違います。親子関係があり親のスコープの識別子は参照できます。
種類 | 説明 |
---|---|
ブロックスコープ | ブロックごとのスコープ。関数やif、forなどのブロック単位。ブロックは{}で囲まれた範囲。 |
ファイルスコープ | ファイルごとのスコープ。ファイル内でインポートしたパッケージを保持する。パッケージ以外は対象としていない。 |
パッケージスコープ | パッケージごとのスコープ。大文字から始まる識別子は他のパッケージからも参照できる。(パッケージ外へのエクスポートを参照) |
ユニバーススコープ | 組み込み型や組み込み関数を保持するスコープ。プログラム実行時からずっとあるスコープ。他のスコープの一番ルートとなる。 |
ブロックスコープ
func f() {
n := 100
println(n)
if true {
n := 200 // 100が入った変数とは別のもの
println(n)
}
}
func g() {
n := 300 // 100,200が入った変数とは別のもの
println(n)
}
ファイルスコープ
// fmtがファイルスコープになる
import "fmt"
パッケージスコープ
package myhex
import "fmt"
type Hex int // パッケージスコープの型
func (h Hex) String() string { // パッケージスコープの関数
return fmt.Sprintf("%x", int(h))
}
ユニバーススコープ
false := true // 定数falseはユニバーススコープ
if false {
fmt.Println(false) // trueが表示される
}
type int string // int型はユニバーススコープ
var n int
fmt.Printf("%q\n", n) // ""が表示される
##パッケージ変数
関数間の変数の共有は、パッケージスコープで変数を宣言すれば可能です。
var msg string = "hello"
func f() { println(msg) }
func main() {
f()
msg = "hi, gophers"
f()
}