LoginSignup
4
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-16

はじめに

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

シリーズの一覧

  1. Goについて知っておく事
  2. 基本構文
  3. 関数と型
  4. パッケージとスコープ(今回)

本記事の内容

今回学ぶ内容は以下の通りです。

パッケージ

パッケージは関数や変数、定数、型を意味のある単位でまとめたもので、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で定義します。

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メソッドが実行される
}

パッケージの実装を複数のファイルで行う

パッケージは複数ファイルで実装して結合することができます。ただし、結合できるのは同じフォルダ内にある場合に限ります。(パッケージを配置するを参照)

myhex/string.go

package myhex // 自作パッケージmyhex
...
myhex/rune.go

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以下の任意の場所に、パッケージ名のフォルダで配置します。
alt

パッケージの初期化

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

依存パッケージの初期化順
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を使うためには環境変数のGO111MODULEonにします。
パッケージをインポートした上で、go mod initコマンドを実行するとgo.modgo.sumファイルが生成されます。
(依存モジュールがない場合はgo.sumは生成されません)

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

go.modファイル内で依存モジュールは以下の形式で管理します。
require <ライブラリ> <バージョン>
バージョンはsemverで記述して、特定のコミットも指定することができます。

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から削除する。
必要なパッケージを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()
}
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1