Goのエラーを直して覚える
実際に出力されるメッセージを読みながら、空欄を埋めて直します。全170問。間違えた問題は翌日・3日後・7日後に自動で再出題されます。
エラー別に調べる
初級
- declared and not used: の原因と直し方Goでは変数を宣言しても一度も使わないとコンパイルエラーになります。デバッグ用に一時的に変数を減らした際など、使い忘れの変数が残っていると発生します。
- imported and not used の原因と直し方importしたパッケージを一度も使わないとコンパイルエラーになります。関数を削除したときにimport文だけ消し忘れると典型的に発生します。
- undefined: の原因と直し方宣言していない変数名や関数名を参照すると undefined エラーになります。変数名のタイプミスが典型的な原因です。
- missing return at end of function の原因と直し方戻り値のある関数は、すべての実行経路の末尾でreturn文が必要です。if文の片方の分岐にしかreturnが無いと、関数全体の末尾に到達しうると判断されエラーになります。
- cannot use ... as ... value(型の不一致)の原因と直し方変数の型と代入しようとする値の型が食い違うと、コンパイル時にcannot useエラーになります。Goは暗黙の型変換を行わないため、型はすべて一致している必要があります。
- non-boolean condition in if statement の原因と直し方C言語やJavaScriptと違い、Goのif文の条件式には真偽値(bool)しか書けません。0や1のような整数をそのまま条件に置くとコンパイルエラーになります。
- no new variables on left side of := の原因と直し方短縮変数宣言 := は左辺に少なくとも1つ新しい変数が必要です。すでに宣言済みの変数だけを左辺に並べて := を使うとコンパイルエラーになります。
- index out of range パニックの原因と直し方存在しない添字でスライスや配列にアクセスすると、コンパイルは通りますが実行時にパニックしてプログラムが異常終了します。
- invalid memory address or nil pointer dereference の原因と直し方値が代入されていないnilのポインタや構造体のフィールドにアクセスすると、実行時にパニックが発生します。newやリテラルで初期化する前のポインタが典型的な原因です。
- integer divide by zero パニックの原因と直し方整数を0で割ると、実行時にパニックが発生しプログラムが異常終了します。0になりうる値は、割り算の前に事前チェックが必要です。
- assignment to entry in nil map の原因と直し方makeで初期化していないnilのマップに要素を書き込もうとすると、実行時にパニックが発生します。マップのゼロ値はnilで、読み取りはできても書き込みはできません。
- slice bounds out of range の原因と直し方スライスをs[a:b]の形で切り出す際、範囲の終端が容量を超えているとパニックします。境界の指定はスライスの長さや容量を超えないことが前提です。
- assignment mismatch: 1 variable but ... returns 2 values の原因と直し方複数の戻り値を持つ関数の結果を、1つの変数だけで受け取ろうとするとコンパイルエラーになります。errorを返す関数を呼ぶときに特に起きやすいミスです。
- invalid operation: mismatched types string and int の原因と直し方文字列と数値を+演算子でそのまま連結しようとすると、型が一致していないためコンパイルエラーになります。数値を文字列に変換してから連結する必要があります。
中級
- strconv.Atoiのerrを無視するとゼロ値のまま処理が進むstrconv.Atoiは変換に失敗するとerrを返しますが、これを無視すると1番目の戻り値は黙って0のままになります。エラーチェックをしないと、変換失敗が気づかれずに処理が進んでしまいます。
- appendの戻り値を再代入し忘れると要素が反映されないappendは新しいスライスを返す関数で、元のスライス変数を直接書き換えません。戻り値を元の変数に代入し直さないと、追加した要素が失われます。
- スライスが元の配列を共有していて予期せぬ書き換えが起きるスライスを別のスライスから切り出すと、多くの場合は同じ配列を参照したままになります。一方を書き換えると、共有している範囲がもう一方にも影響します。
- クロージャがループ外の変数を共有キャプチャして最終値しか残らないforループの外側で宣言した変数をクロージャの中で使い、そのクロージャをスライスに貯めていく場合、各クロージャは同じ変数を参照し続けます。実行時にはループ終了後の最終値だけが全クロージャに共通して見えます。
- all goroutines are asleep - deadlock! の原因と直し方バッファなしチャネルへの送信は、受信するgoroutineが存在するまでブロックします。受信側のgoroutineを起動し忘れると、送信だけが永遠にブロックしデッドロックとして検出されます。
- send on closed channel パニックの原因と直し方close()で閉じた後のチャネルに値を送信しようとすると、実行時にパニックが発生します。チャネルを閉じるのは送信側の役割で、閉じた後は一切送信できません。
- close of closed channel パニックの原因と直し方一度closeしたチャネルをもう一度closeしようとすると、実行時にパニックが発生します。複数箇所からcloseを呼びうるコードでは、どこか1箇所だけが責任を持つ設計にする必要があります。
- 型付きnilをinterfaceに入れると == nil が false になるnilのポインタをerrorなどのinterface型の変数に代入すると、そのinterfaceは型情報を持った「型付きnil」になります。値自体はnilでも、interfaceとしてはnilと等しくなくなります。
- interface conversion panic の原因と直し方型アサーション v.(T) は、vの実際の型がTでない場合に実行時パニックを起こします。2つ目の戻り値(ok)を受け取らずに単一戻り値の形で書くと、失敗時にそのままパニックします。
- sync: negative WaitGroup counter パニックの原因と直し方sync.WaitGroupのDone()はAdd()で加算した回数より多く呼ぶとカウンタが負になり、実行時にパニックします。goroutineの数とAddで加算する数が一致していないと発生します。
- json.Unmarshalで非公開(小文字始まり)フィールドが無視されるencoding/jsonパッケージは、構造体の小文字始まりの非公開フィールドを読み書きできません。JSONのキーに対応するフィールド名を小文字で書いてしまうと、値が入らないままゼロ値になります。
- len(s)がUTF-8のバイト数を返し文字数と一致しないGoの文字列はUTF-8のバイト列として保持されており、len(s)は文字数ではなくバイト数を返します。日本語のようなマルチバイト文字を含む文字列では、見た目の文字数より大きな値になります。
上級
- 値レシーバのメソッドは呼び出し元の値を変更しない値レシーバで定義したメソッドは、レシーバのコピーに対して操作を行います。メソッド内でフィールドを変更しても、呼び出し元の元の変数には反映されません。
- ポインタレシーバのメソッドしか無い型は値ではinterfaceを満たさないポインタレシーバで定義したメソッドは、その型の値そのもの(非ポインタ)のメソッドセットには含まれません。値をそのままinterface型の変数に代入しようとするとコンパイルエラーになります。
- スライスやマップを含む構造体は == で比較できないスライスやマップ、関数値をフィールドに持つ構造体は比較不可能な型として扱われ、==演算子で比較しようとするとコンパイルエラーになります。
- recoverは同じgoroutine内のpanicしか捕まえないrecoverはそれを呼び出したgoroutine内で発生したpanicしか回復できません。別のgoroutine内でdeferとrecoverを仕込んでも、他のgoroutineのpanicは捕まえられずプログラム全体がクラッシュします。
- 埋め込み構造体のメソッドが外側の同名メソッドにシャドーイングされる構造体に別の構造体を埋め込むと、埋め込み側のメソッドが昇格して呼び出せるようになります。しかし外側の構造体が同名のメソッドを定義していると、そちらが優先され埋め込み側のメソッドは隠れてしまいます。
- does not satisfy(ジェネリクスの制約違反)の原因と直し方ジェネリック関数の型パラメータには制約(constraint)を指定できます。制約が要求する演算(比較や算術演算など)をサポートしない型を渡すと、コンパイルエラーになります。
- deferの引数はdefer文を実行した時点の値で評価されるdefer文に渡した関数呼び出しの引数は、実際に関数が実行される(関数の終了時)タイミングではなく、defer文自体が実行された時点で評価され確定します。
- 名前付き戻り値が内側のブロックで:=によりシャドーイングされる名前付き戻り値の変数と同じ名前をif文などの内側のブロックで:=を使って再宣言すると、それは別の新しいローカル変数になります。裸のreturnではブロック外の元の名前付き戻り値の値が返され、内側で設定した値は反映されません。