Menu

Go言語の変数:var、:=、ゼロ値、シャドーイング

Goでvarと短い形式の:=を使って変数を宣言する方法、ゼロ値とは何か、多重代入、そしてGoを始めたばかりの人がはまるシャドーイングと未使用変数のエラーを解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

Goで変数を宣言する方法は2つあります。どこでも使える var と、関数の中で使えて型を推論する短い形式の := です。

どの変数もコンパイル時に決まる固定の型を持ちます。値を与えるとGoはそこから型を推論するので、型まで書く(var city string = "Lisbon")のは正しいものの冗長です。

varと:=の違い

var:=
使える場所パッケージレベルと関数の中関数の中だけ
省略可。値がないときは必須常に推論される
初期値省略可(なければゼロ値)必須
再宣言できない左辺に新しい変数が1つ以上あればできる

関数の中でのGoらしい使い分けは次のとおりです。

  • 初期値があるときは x := value
  • ゼロ値がほしいときは var x T。「意図的に空」であることを示します:var buf bytes.Buffervar 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
boolfalse
string""
ポインタ、スライス、マップ、チャネル、関数、インターフェースnil
配列、構造体すべての要素やフィールドがそれぞれのゼロ値

よくできたGoのAPIは、ゼロ値がそのまま役に立つように設計されています。ゼロ値の sync.Mutex はロックされていない状態で、ゼロ値の bytes.Buffer は空ですぐ使え、lenrangeappend は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 の中の := は、内側のブロックにはまだどちらも存在しなかったので、counterr の両方を新しい変数として宣言しました。問題なくコンパイルでき、「値が消えた」というバグの典型的な原因になります。

直すには、先に err を宣言して = で代入します。

var err error
count, err = strconv.Atoi(input)

go vet はデフォルトではシャドーイングを報告しません。別の shadow アナライザーが報告し、golangci-lint などのリンターで有効にできます。

名前の付け方

  • camelCase を使います:maxRetriesuserIDmax_retries ではありません。
  • 頭字語は大文字小文字をそろえます:userId ではなく userIDhttpClientparseURL
  • スコープが短ければ短い名前にします。ループでは i、リーダーは r、エラーは err。パッケージレベルの変数には、長めで説明的な名前を付けます。
  • パッケージレベルの大文字の名前(var Timeout)は他のパッケージに公開されます。パッケージとインポートのページを参照してください。

パッケージレベルの変数

関数の外で宣言した変数はプログラムの実行中ずっと存在し、main の実行前に依存関係の順に初期化されます。設定やキャッシュに便利で、すべてのゴルーチンから共有されます。複数のゴルーチンが書き込むなら、ミューテックスで保護するか sync/atomic を使ってください。そうしないとデータ競合になります。

変わることのない値には定数を使いましょう。const はうっかり変更されることがなく、実行時のコストもかかりません。

よくある質問

Goのvarと:=の違いは何ですか?

:= は型を推論して変数を宣言・初期化するもので、関数の中でしか使えません:count := 10var はどこでも(パッケージレベルでも)使え、型を明示でき、初期値を省略するとゼロ値になります:var count int。関数の中では、初期値があるときは :=、ゼロ値にしたいときは var を使うのがGoらしい書き方です。

Goのゼロ値とは何ですか?

値を指定せずに宣言した変数には、その型のゼロ値が入ります。数値は 0boolfalsestring""、ポインタ、スライス、マップ、チャネル、関数、インターフェースは nil です。構造体は各フィールドがゼロ値になります。Goに初期化されていない変数はありません。

Goの「declared and not used」はどう直せばよいですか?

Goは、代入されたのに一度も読まれないローカル変数を拒否します。使うか、削除するか、デバッグ中なら _ = x でブランク識別子に代入します。パッケージレベルの変数と関数の引数はこのルールの対象外です。

Goでグローバル変数を宣言するには?

関数の外、パッケージレベルで var を使います:var counter int。そこでは短い形式の := は使えません。大文字の名前(var Counter int)は他のパッケージから見え、小文字の名前はそのパッケージの中からしか見えません。ゴルーチンから書き込む場合は、パッケージレベルの変数をミューテックスで保護してください。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める