Goで変数を宣言する方法は2つあります。どこでも使える var と、関数の中で使えて型を推論する短い形式の := です。
どの変数もコンパイル時に決まる固定の型を持ちます。値を与えるとGoはそこから型を推論するので、型まで書く(var city string = "Lisbon")のは正しいものの冗長です。
varと:=の違い
var | := | |
|---|---|---|
| 使える場所 | パッケージレベルと関数の中 | 関数の中だけ |
| 型 | 省略可。値がないときは必須 | 常に推論される |
| 初期値 | 省略可(なければゼロ値) | 必須 |
| 再宣言 | できない | 左辺に新しい変数が1つ以上あればできる |
関数の中でのGoらしい使い分けは次のとおりです。
- 初期値があるときは
x := value。 - ゼロ値がほしいときは
var x T。「意図的に空」であることを示します:var buf bytes.Buffer、var total int。 - 推論される型が望みの型ではないときは
var x T = value。たとえばvar ratio float64 = 1です(単にratio := 1と書くとintになります)。
関数の外で := を使うと構文エラーになります。
./main.go:5:1: syntax error: non-declaration statement outside function body
ゼロ値
値を指定せずに宣言した変数に、でたらめな値が入っていることはありません。その型のゼロ値を持ちます。
| 型 | ゼロ値 |
|---|---|
| 整数、浮動小数点数 | 0 |
bool | false |
string | "" |
| ポインタ、スライス、マップ、チャネル、関数、インターフェース | nil |
| 配列、構造体 | すべての要素やフィールドがそれぞれのゼロ値 |
よくできたGoのAPIは、ゼロ値がそのまま役に立つように設計されています。ゼロ値の sync.Mutex はロックされていない状態で、ゼロ値の bytes.Buffer は空ですぐ使え、len、range、append はnilスライスでも動きます。注意が必要なのはマップです。nilマップからの読み取りはゼロ値を返しますが、書き込むとpanicします。値を格納する前に、make かリテラルでマップを作ってください。
複数の変数を宣言する
var ( ... ) ブロックは、関連するパッケージレベルの変数をまとめます。x, y = y, x のような多重代入では、何かが代入される前に右辺の式がすべて評価されます。値の入れ替えがうまくいくのはこのためです。
複数の名前を使った := は、左辺の名前のうち 少なくとも1つ が新しければ、何も再宣言しません。同じスコープにすでにある名前は、単に代入されるだけです。これによって err を使い回せます。
f, err := os.Open("a.txt") // declares f and err
g, err := os.Open("b.txt") // declares g, assigns to the existing err
新しい名前が1つもないとエラーになります。
./main.go:7:4: no new variables on left side of :=
すでにある変数に代入するときは、普通の = を使います。
未使用の変数はエラー
宣言したのに一度も読まれないローカル変数があると、ビルドが止まります。
./main.go:9:2: declared and not used: x
オフにできる警告ではありません。変数を削除するか、使うか、デバッグ中ならブランク識別子で黙らせます:_ = x。ブランク識別子は、不要な戻り値を捨てるのにも使えます:_, err := fmt.Println("hi")。
パッケージレベルの変数と関数の引数は、このルールの対象外です。
スコープとシャドーイング
変数は、宣言した場所から、それを囲むブロック({ ... })の終わりまで存在します。内側のブロックで := を使うと、外側に同じ名前の変数があっても 新しい 変数が作られます。外側の変数は隠される(シャドーイングされる)だけで、変更されません。
このプログラムは inside: 42 と表示し、続いて outside: 0 と表示します。if の中の := は、内側のブロックにはまだどちらも存在しなかったので、count と err の両方を新しい変数として宣言しました。問題なくコンパイルでき、「値が消えた」というバグの典型的な原因になります。
直すには、先に err を宣言して = で代入します。
var err error
count, err = strconv.Atoi(input)
go vet はデフォルトではシャドーイングを報告しません。別の shadow アナライザーが報告し、golangci-lint などのリンターで有効にできます。
名前の付け方
camelCaseを使います:maxRetries、userID。max_retriesではありません。- 頭字語は大文字小文字をそろえます:
userIdではなくuserID、httpClient、parseURL。 - スコープが短ければ短い名前にします。ループでは
i、リーダーはr、エラーはerr。パッケージレベルの変数には、長めで説明的な名前を付けます。 - パッケージレベルの大文字の名前(
var Timeout)は他のパッケージに公開されます。パッケージとインポートのページを参照してください。
パッケージレベルの変数
関数の外で宣言した変数はプログラムの実行中ずっと存在し、main の実行前に依存関係の順に初期化されます。設定やキャッシュに便利で、すべてのゴルーチンから共有されます。複数のゴルーチンが書き込むなら、ミューテックスで保護するか sync/atomic を使ってください。そうしないとデータ競合になります。
変わることのない値には定数を使いましょう。const はうっかり変更されることがなく、実行時のコストもかかりません。
よくある質問
Goのvarと:=の違いは何ですか?
:= は型を推論して変数を宣言・初期化するもので、関数の中でしか使えません:count := 10。var はどこでも(パッケージレベルでも)使え、型を明示でき、初期値を省略するとゼロ値になります:var count int。関数の中では、初期値があるときは :=、ゼロ値にしたいときは var を使うのがGoらしい書き方です。
Goのゼロ値とは何ですか?
値を指定せずに宣言した変数には、その型のゼロ値が入ります。数値は 0、bool は false、string は ""、ポインタ、スライス、マップ、チャネル、関数、インターフェースは nil です。構造体は各フィールドがゼロ値になります。Goに初期化されていない変数はありません。
Goの「declared and not used」はどう直せばよいですか?
Goは、代入されたのに一度も読まれないローカル変数を拒否します。使うか、削除するか、デバッグ中なら _ = x でブランク識別子に代入します。パッケージレベルの変数と関数の引数はこのルールの対象外です。
Goでグローバル変数を宣言するには?
関数の外、パッケージレベルで var を使います:var counter int。そこでは短い形式の := は使えません。大文字の名前(var Counter int)は他のパッケージから見え、小文字の名前はそのパッケージの中からしか見えません。ゴルーチンから書き込む場合は、パッケージレベルの変数をミューテックスで保護してください。