ソースコードの表現
GoのソースコードはUTF-8でエンコードされたUnicodeテキストです。大文字と小文字は別の文字として扱われます。
コメント
コメントは、行コメント(//から行末まで)とブロックコメント(/*から*/まで)の2種類です。
// 行コメント
/*
ブロックコメント
*/トップレベル宣言の直前に改行なしで置かれたコメントは、その宣言のドキュメントとみなされます。
// Add はaとbの和を返す。
func Add(a, b int) int {
return a + b
}識別子とキーワード
識別子は、変数・関数・型・パッケージなどに付ける名前です。文字で始まり、文字と数字からなります。アンダースコア_は、仕様上「小文字の文字」として扱われます。そのため識別子の先頭にも使え、先頭が_の名前は小文字始まりと同じく他パッケージからは見えません(公開されるのは先頭がUnicodeの大文字のときだけです)。
var _count int // 先頭が_なのでパッケージ外からは見えない
var count int // 先頭が小文字でも同様に見えない
var Count int // 先頭が大文字なら他パッケージから見える次の語はキーワードであり、識別子には使えません。
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return varセミコロンの自動挿入
文法上、Goの文はセミコロンで終わります。字句解析は、コンパイラがソース文字列をifや:=などのトークンに分ける処理です。この段階で次のトークンの直後に改行やEOFがあればセミコロンを補うため、ソースに;を書く必要はほとんどありません。ソースに;を書いた場合もそれはセミコロンのトークンとして読まれ、行末が;の行では自動挿入は走らないため、二重にはなりません。
次のトークンの直後の改行でセミコロンが入ります。
- 識別子、リテラル
break,continue,fallthrough,return++,--,),],}
if条件式の直後で改行すると、行頭の{の前にセミコロンが入ったと解釈されコンパイルエラーになります。制御構文の{は同じ行に書きます。
if x < 0 {
return -x
}if x < 0
{
// コンパイルエラー
}変数宣言
varで型と初期値を明示できます。初期値を省略するとゼロ値が入ります。型を省略すると右辺から推論します。関数内では:=による短い変数宣言も使えます。
var x int = 10
var y = 20 // 型推論でint
z := 30 // 短い変数宣言(関数内のみ)パッケージ直下では:=は使えず、varが必要です。
package demo
var count = 0 // OK
// n := 1 // コンパイルエラー: パッケージレベルでは:=不可宣言した変数を使わないとコンパイルエラーになります。意図的に捨てるときは_へ代入します。
x := 1
_ = x複数宣言
同じブロック内で複数の識別子をまとめられます。
var (
host = "localhost"
port = 8080
)複数の戻り値を、左辺の変数に同時に受け取れます。すでに宣言済みの変数への再代入は=です。
x, y = 1, 2
a, b = b, a関数から複数の値を受け取るときも、宣言済みなら=で再代入します。
func swap(a, b int) (int, int) { return b, a }
x, y := 1, 2
x, y = swap(x, y) // 2, 1命名の慣習
複数語の名前はアンダースコアではなくMixedCapsまたはmixedCapsを使います。短いスコープではi、okのような短い名前もよく使われます。
ゲッターにはGetプレフィックスを付けない慣習があります。フィールドownerならメソッドはOwnerとします。
ブロック
ブロックは宣言と文のグループです。波括弧{ }で囲みます。関数本体、制御構文の本体、ifの初期化文を含む全体などがブロックを形成します。
スコープ
識別子は宣言されたブロックとその内側のブロックで有効です。内側のブロックで同名を宣言すると、外側の名前はそのブロック内では隠れます。
x := 1
{
x := 2
fmt.Println(x) // 2
}
fmt.Println(x) // 1再宣言と:=
:=は、左辺に少なくとも1つ新しい変数があるときに使います。すでにそのブロックで宣言されている名前には代入され、それ以外は新たに宣言されます。
x, y := 1, 2
x, z := 3, 4 // xは代入、zは新規ブランク識別子
_はブランク識別子です。主な用途は次の3つです。
- 値の破棄:
_, err := do() - 副作用のみのimport:
import _ "image/png" - コンパイル時の型チェック:
var _ io.Reader = (*MyType)(nil)
未使用importはコンパイルエラーです。_でimportすると、パッケージのinitだけが実行され、名前は使いません。
ラベルのスコープ
ラベルは、Outer:のように文の前に付ける名前です。入れ子のループで外側へ抜けるときはbreak Outer、外側ループの次の反復へ進むときはcontinue Outerと書きます。gotoの飛び先にも使います。
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue Outer // 内側を抜け、外側ループの次のiへ
}
}
}i := 0
Again:
fmt.Println(i)
i++
if i < 3 {
goto Again
}ラベルはそれを含む関数内でのみ有効です。別の関数のラベルには飛べません。
ゼロ値
宣言だけして値を入れない変数にはゼロ値が入ります。
| 型 | ゼロ値 |
|---|---|
| 数値 | 0 |
bool | false |
string | "" |
| ポインタ、スライス、マップ、チャネル、関数、インターフェース | nil |
| 構造体 | 各フィールドのゼロ値 |
| 配列 | 各要素のゼロ値 |
未初期化変数
Goには「未初期化のローカル変数」がありません。宣言された識別子は常にゼロ値か、明示的な初期化値を持ちます。これにより、読み取り前の不定値によるバグを減らす設計です。
ポインタ・スライス・マップ・チャネル・関数・インターフェースのゼロ値はnilです。nilは型ごとに「まだ使うための内部のデータ構造が用意されていない」状態を表します。操作がその内部構造への読み書きを前提にするとパニックになります。内部構造がなくても意味がある操作や、必要ならこちらで確保する組み込み(appendなど)は動くことがあります。
nilだからどの操作もパニックになるわけではなく、型と操作の組み合わせで決まります。たとえばnilマップへの書き込みはハッシュ表が無いためパニックですが、nilスライスへのappendは要素を格納する配列を新しく確保して動きます。マップを書き込みに使うにはmakeかリテラルでの初期化が必要です。
var s []int
s = append(s, 1) // 要素を格納する配列を確保して動く
var m map[string]int // m == nil
// m["x"] = 1 // ハッシュ表が無いのでpanic
m = make(map[string]int) // または m = map[string]int{}
m["x"] = 1定数宣言
定数はコンパイル時に評価できる式に限られます。変数の参照や関数呼び出しは定数式にならず、コンパイルエラーになります。
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
var limit = 10
// const max = limit // コンパイルエラー: 変数は定数式に使えない
// const n = len([]int{1, 2, 3}) // コンパイルエラー: スライスの長さは定数にならない
// const now = time.Now() // コンパイルエラー: 実行時の値は使えないiota
定数宣言ブロック内でiotaは行ごとに0から増える連番です。新しいconst (ブロックを開くとiotaは0にリセットされます。ファイル全体で増え続けるわけではありません。
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)式と組み合わせた例です。各行でiotaの値が先に決まり、その行の式に代入されます。
const (
_ = iota // 0を捨てる
KB = 1 << (10 * iota) // 1024
MB // 1048576
GB // 1073741824
)パッケージレベルの変数初期化のあと、initが自動実行されます。
init関数
パッケージ内にfunc init()を複数定義できます。initはインポートや変数の初期化のあと、自動的に実行されます。
func init() {
log.SetFlags(log.LstdFlags)
}initに重い副作用やグローバル状態の変更を詰め込むと、テストやimport順序に影響しやすくなります。パッケージの最低限の登録に留めるのが無難です。
パッケージの初期化順序
パッケージの初期化は次の順序です。
- インポートされたパッケージ(依存関係の順)
- パッケージレベルの変数(宣言順)
init関数(ソースファイル内の出現順)
例:
main が import → config
config が import → log実行順: logの変数 → logのinit → configの変数 → configのinit → mainの変数 → mainのinit → main.main
同一パッケージ内では、コンパイル単位(ファイル)の並びと各ファイル内の出現順に依存します。初期化順に依存する設計は避けます。
mainパッケージの初期化が終わるとmain関数が呼ばれます。