エラーの内容
go buildまたはgo runを実行すると、コンパイラが突然停止します:
./main.go:12:5: undefined: SomeVariable
./main.go:20:10: undefined: SomeFunction
./utils.go:8:3: undeclared name: helperFunc
警告もなく、部分的なコンパイルもなく、ただ強制終了します。Goは参照しているものが見つからない場合、バイナリの生成を拒否します。これはバグではなく、れっきとした仕様です。
なぜこのエラーが起きるのか
PythonやJavaScriptとは異なり、Goはすべての名前をコンパイル時に解決します。宣言の漏れはランタイム時の驚きではなく、コンパイラが即座に検出します。つまり、タイポ、間違ったパッケージ、スコープのミス、ファイルの欠如はすべて、コードが実行される前にundefinedエラーとして表面化します。
よくある原因:
- 名前のタイポ(大文字・小文字も区別されます —
myFuncとMyFuncは別物です) - 呼び出しているパッケージのimportが漏れている
- 変数をブロック内で宣言し、ブロック外で使用している
- 小文字(未エクスポート)の名前を別パッケージからアクセスしている
- ビルドタグによってファイルが暗黙的に除外されている
- ファイル内の
package宣言が間違っている
ステップごとの修正方法
1. まずタイポを確認する
正直に言うと、約70%のケースでこれが原因です。Goは大文字・小文字を区別するため、myFunc、MyFunc、myfuncはまったく異なる3つの名前です。
// 誤り
result := myFuncion() // タイポ:'Function'の't'が抜けている
// 正しい
result := myFunction()
宣言箇所と呼び出し箇所の両方でスペルを確認してください。VS CodeやGoLandでは、赤い下線にカーソルを合わせると最も近い名前の候補がツールチップに表示され、タイポがすぐに分かります。
2. importを確認する
別パッケージの関数をimportせずに呼び出すと、標準ライブラリのパッケージであってもundefinedとしてGoに報告されます。
// 誤り — importが不足
func main() {
fmt.Println("hello") // undefined: fmt
}
// 正しい
import "fmt"
func main() {
fmt.Println("hello")
}
最も素早い修正方法はgoimportsです:
goimports -w .
Go拡張機能を入れたVS CodeやGoLandは、保存時に不足しているimportを自動的に追加するため、この問題はプレーンテキストエディタを使う人にほぼ限定されます。
3. インポートパスだけでなくパッケージ名を確認する
これはよく陥るトラップです:正しいパスをimportしているのに、間違った名前で呼び出してしまうケースです。
import "math/rand"
// 誤り — パッケージ名は'math'ではなく'rand'
result := math.Intn(100) // undefined: math
// 正しい
result := rand.Intn(100)
使用する識別子はパッケージの名前であり、インポートパスではありません。math/rand→randのように名前とパスが異なる場合は、ソースファイルの先頭にあるpackage宣言を確認して実際の名前を調べましょう。
4. スコープを確認する — 変数は想定した場所にあるか?
Goのブロックスコープは厳格です。if、for、またはいずれかの{}ブロック内で変数を宣言すると、そのブロックの外には存在しません。
// 誤り
if condition {
result := compute()
}
fmt.Println(result) // undefined: result
// 正しい — ブロックの前に宣言する
var result int
if condition {
result = compute()
}
fmt.Println(result)
短縮宣言演算子:=が落とし穴になります。ブロック内でのresult := compute()は、そのブロックにスコープされた全く新しい変数を作成します。外側の変数への代入に見えますが、そうではありません。
5. パッケージ間でのエクスポート名と未エクスポート名
Goの可視性ルールはシンプルです:大文字始まり = エクスポート済み(public)、小文字 = 未エクスポート(パッケージプライベート)。パッケージ外から未エクスポートの名前を呼び出そうとするとundefinedになります。
// 'utils'パッケージ内:
func helperFunc() string { ... } // 未エクスポート — 'utils'内でのみ参照可能
func HelperFunc() string { ... } // エクスポート済み — どこからでも参照可能
// main.goの場合:
utils.helperFunc() // undefined: utils.helperFunc
utils.HelperFunc() // OK
パッケージを自分で管理している場合は、名前を大文字始まりにしてエクスポートしましょう。サードパーティのパッケージの場合は、エクスポートされた同等の関数や別のAPIを探してください。
6. ビルドタグによるファイルの除外
ビルドタグによってファイルがコンパイルから暗黙的に除外されることがあります。そのファイルに他のファイルが依存する宣言が含まれている場合、明確な理由もなくundefinedが発生します。
//go:build linux
package mypackage
func PlatformSpecificFunc() { ... }
macOSやWindowsでビルドすると、コンパイラの観点からはPlatformSpecificFuncが存在しないことになります。ファイルの先頭にある//go:buildタグ(または古い// +build構文)を確認してください。コンパイルに含まれるファイルを正確に確認するには:
go list -f '{{.GoFiles}}' .
go list -f '{{.IgnoredGoFiles}}' . # ビルドタグで除外されたファイル
7. ファイル内の誤ったpackage宣言
ディレクトリ内のすべての.goファイルは同じパッケージ名を宣言しなければなりません(テストファイルを除く)。1つでもpackage行が一致しないと、コンパイラはそのファイルを別パッケージに属するものとして扱い、その宣言が隣のファイルから見えなくなります。
// ファイル: utils.go
package util // 誤り — パッケージ名が'utils'の場合
// 正しい
package utils
go vet ./...を実行してください。これによってこの問題や類似のミスが自動的に検出されます。
8. 循環インポートによる宣言の隠蔽
Goは循環インポートを完全に禁止しています。パッケージAがBをインポートし、BがAをインポートすると、両方のコンパイルが失敗します。エラーが明確な循環インポートメッセージではなく、インポートしたパッケージのシンボルに対するundefinedとして現れることがあります。AとBが互いにインポートしない第三のパッケージに共有型を移動することで、循環を解消しましょう。
修正を確認する
変更を加えたら、クリーンリビルドで確認しましょう:
# ビルドキャッシュを削除してすべて再ビルド
go clean -cache
go build ./...
# または直接実行
go run main.go
# モジュール内の全パッケージをリント
go vet ./...
クリーンな出力(メッセージが何もない)は成功を意味します。まだエラーが表示される場合は、コンパイラの出力を注意深く読んでください。正確なファイルパスと行番号が含まれており、曖昧さは一切なく、常に正しい場所を指し示します。
クイックチェックリスト
- 大文字・小文字を含め、名前を正確にスペルしているか?
- それを定義するパッケージをimportしているか?
- 正しいパッケージエイリアスを使用しているか(例:
mathではなくrand)? - 変数を正しいスコープで宣言しているか?
- 別パッケージから使用する場合、名前はエクスポート済み(大文字始まり)か?
- ファイルを暗黙的に除外するビルドタグはないか?
- ディレクトリ内のすべてのファイルが同じ
package名を共有しているか?
便利なツール
# importを自動修正
goimports -w .
# 静的解析(undefinedに関連するミスを多数検出)
go vet ./...
staticcheck ./...
# コンパイルされるファイルを正確に確認
go list -f '{{.GoFiles}}' .
go list -f '{{.IgnoredGoFiles}}' . # ビルドタグで除外されたファイル
VS Code、Vim/Neovim、GoLandで使用されるgopls言語サーバーは、コンパイラを実行する前にリアルタイムで未定義の名前をハイライトします。一度設定すれば、これらのエラーのほとんどはビルド時の数分後ではなく、入力した瞬間に検出されます。

