2020-07-29

エラーで学ぶGolang

エラーで学ぶReactJSに引き続き、Golangで躓いたことをまとめてみました。 エラーで学ぶReactJSReactJS で遭遇したエラーと警告をまとめました。解説と解決策もあります。https://note.crohaco.net/2018/react-errors-warnings/

とはいっても完成ではなく、常にWIPとして新たに何かあれば追記していくと思います。

適当に羅列しても良かったんですが流石に長いので雑にカテゴリを区切りました。 量が多いからまだ見づらいかも..

というわけでどうぞ。 間違っている所があればコメントやTwitterで指摘ください。

役に立ったという方は拡散したり私に案件を紹介してくれてもいいんだからね!(未承諾広告)

文字列関連

rune型に一文字以外を指定する

エラー
  • rune は int32 で、 一文字以外の文字列を指定できません。 0文字(空文字) であってもエラーになります。
    • cannot convert 'u0000' (type untyped number) to type string
    • empty character literal or unescaped ' in character literal
    • invalid character literal (more than one character)
    • illegal rune literal
対応
  • rune型に初期値を指定する場合は 0 を指定しましょう。
NG
  • r := ''
NG
  • r := 'test'
OK
  • r := 'a'
OK
  • var r rune r = 0
参考

文字列からruneスライスを作るときに{}で初期化しようとした

エラー
  • cannot convert "" (type untyped string) to type rune
NG
  • test = []rune{""}
OK
  • test = []rune("")
参考

%フォーマットの数に対して引数の数が一致していない

警告
  • %フォーマットの数と引数の数が一致しないと警告が発生します。
    • Printf format %s reads arg #2, but call has 1 arg
    • Printf call needs 2 args but has 3 args
NG
  • fmt.Printf("%s %s", "a")
NG
  • fmt.Printf("%s %s", "a", "b", "c")
OK
  • fmt.Printf("%s %s", "a", "b")

フォーマット関数の%sが文字列以外を受け取った

警告
  • PrintfやSprintf の %s は string 型を期待しています。
    • Sprintf format %s has arg value of wrong type *string
    • Printf format %s has arg value of wrong type *string
NG
  • fmt.Sprintf("%s", pointer)
OK
  • fmt.Sprintf("%s", *pointer)
info
  • *string (ポインタ)型であってもそのままでは扱えません。
  • ポインタが指す値を引数に指定しましょう。
warning
  • flagパッケージを使ってコマンドライン引数を受け取る場合、その変数はポインタ型なので注意しましょう。

関数関連

返却値のない関数の返却値を期待した

エラー
  • 返却値を返さない関数 (void型)関数の返却値を変数に格納したり参照しようとするとこのエラーが発生します。
    • world() used as value
NG
  • func main() { fmt.Println("Hello ", world()) } func world() { fmt.Println("World") }
OK
  • func main() { fmt.Print("Hello ") world() } func world() { fmt.Println("World") }

返却値数と左辺の数があっていない

エラー
  • 関数の返却値とそれを受ける左辺の変数の数があっていないとエラーになります
    • assignment mismatch: 1 variable but test returns 2 values
NG
  • func test() (int, int) { return 1, 2 } func main() { a := test() }
OK
  • func test() (int, int) { return 1, 2 } func main() { a, b := test() }

:= での変数定義を関数外で行った

エラー
  • := でグローバル変数を定義することはできないようです
    • syntax error: non-declaration statement outside function body
対応
  • 関数内でのみ必要な変数は関数内で定義するか、 var で定義しましょう。
NG
  • v := 1
OK
  • func Function() { v := 1 }
OK
  • var v = 1

インターフェースに定義されたメソッドが期待通りに登録されていない

エラー
  • インタフェースに定義したメソッドと名称や型が異なるとエラーになります。
    • cannot use (testStruct1 literal) (value of type testStruct1) as testInterface value in assignment: missing method test
      • cannot use testStruct1 literal (type testStruct1) as type testInterface in assignment
        • testStruct1 does not implement testInterface (missing test method)
        • testStruct1 does not implement testInterface (wrong type for test method)
          • have test(int) string, want test(int) int
          • have test(string) int, want test(int) int
インタフェースの定義例
  • type testInterface interface { test(int) int } var i testInterface
  • 上記は構造体に test というメソッドが存在することを強制するインタフェースとそれが強制された変数です
  • このコードを使ってOKとNGを見ていきましょう
NG
  • type testStruct struct{} func main() { i = testStruct{} }
NG
  • type testStruct struct{} func (t testStruct) test(s string) int { return 1 } func main() { i = testStruct{} }
NG
  • type testStruct struct{} func (t testStruct) test(i int) string { return "a" } func main() { i = testStruct{} }
OK
  • type testStruct struct{} func (t testStruct) test(i int) int { return 1 } func main() { i = testStruct{} }
info
  • なお、インターフェースではメソッドを定義させないことは強制できません。

関数定義と異なる数の返却値をreturnした

エラー
  • 定義した関数の返却数とreturn する値の数があっていなければエラーになります
    • too many arguments to return
    • missing return at end of function
NG
  • func test() int { fmt.Println("test") // 返却値が足りない }
NG
  • func test() { return 1 }
NG
  • func test() int { return 1, 2 }
OK
  • func test() int { return 1 }
OK
  • func test() (int, int) { return 1, 2 }

公開されている関数にはコメントが書かれているべき

Golangでは大文字で始まる変数や関数は別パッケージから参照できます。

この状態をエクスポートされた状態(exported) と呼びます。フロントエンドでJSとかやってる方だとすぐ飲み込めますね。

警告
  • 公開されている関数にコメントが書かれていないと警告が出ます
    • exported function ParseCommand should have comment or be unexported
対応
  • 上に 関数名[半角スペース]コメント というフォーマットでコメントが書きましょう。
NG
  • func Test() string { }
NG
  • // this is test func Test() string { }
OK
  • // Test this is test func Test() string { }

printlnビルトイン関数の中でスライスを展開しようとした

エラー
  • 理由はよくわからないんですが、 println 関数の中でスライスの展開はできないようです。
    • invalid use of ... with builtin println
対応
  • 変数をデバッグする場合は、 fmt.Println を使いましょう。
NG
  • println([]string{"a", "b"}...)
OK
  • fmt.Println([]string{"a", "b"})

defer 文で関数を呼び出していない

エラー
  • defer 文は後処理として呼び出されますが、関数を呼び出し形式で指定する必要があります。
    • function must be invoked in defer statement
NG
  • defer 1
NG
  • defer callback
OK
  • defer callback()
warning
  • 上記はdefer文のみを書いていますが、本来は関数内に記述されている必要があります。
  • 関数外に記述すると syntax error です syntax error: non-declaration statement outside function body

型関連

インタフェース型を変換できなかった

パニック
  • interface conversion: interface {} is main.Hex, not int
エラー
  • impossible type assertion: int does not implement
    • cannot have dynamic type int

インタフェース型の変数を型アサーションする場合は変数に入れた値と同じ型でなければなりません。 エイリアス型の場合はエイリアス型でキャストしなければエラーになります。

今回は共通で Hex という int のエイリアス型とメソッドが定義されている前提で話を進めます。

type Hex int func (h Hex) String() string { return fmt.Sprintf("%x", int(h)) }
NG
  • func main() { var h interface{} = Hex(100) fmt.Println(h.(int)) }
OK
  • func main() { var h interface{} = Hex(100) fmt.Println(h.(Hex)) }

なお i, _ := h.(type) のように変数に格納すればパニックは発生しません。 しかし変換できない場合はその型のゼロ値が格納されます。

ビルド時にエラーになるのは、インタフェースのメソッドセットが空でない場合です。

以下のようなインタフェースを用います。

type Stringer interface { String() string }

条件は先程と同様です。

NG
  • func main() { var s Stringer = Hex(100) i, _ := s.(int) fmt.Println(i) }
OK
  • func main() { var s Stringer = Hex(100) i, _ := s.(Hex) fmt.Println(i) }

レシーバの型がポインタなのに呼び出し元の型がアドレスになっていない

どうやら、インタフェースのメソッドセットを満たしているかどうかにレシーバの型が関係してくるようです。

エラー
  • レシーバをポインタ型で定義して、ポインタでない変数からメソッドを呼び出すとエラーになります。
    • cannot use hello literal (type hello) as type Hello in assignment
      • hello does not implement Hello (World method has pointer receiver)
対応
  • レシーバがポインタ型の場合は変数自体をポインタにしてあげるか、レシーバを値にするかのいずれかのようです。 何なんこの仕様。
NG
  • package main import ( "fmt" ) func main() { var h Hello h = hello{} fmt.Println("Hello", h.World()) } type Hello interface { World() string } type hello struct {} func (h *hello) World() string { return "world" }
OK
  • package main import ( "fmt" ) func main() { var h Hello h = &hello{} // ここをアドレス型にする fmt.Println("Hello", h.World()) } type Hello interface { World() string } type hello struct {} func (h *hello) World() string { return "world" }
OK
  • package main import ( "fmt" ) func main() { var h Hello h = hello{} fmt.Println("Hello", h.World()) } type Hello interface { World() string } type hello struct {} func (h hello) World() string { // ここを値型にする return "world" }
warning
  • 後者の対応は(値渡しになるため)フィールドの編集ができなくなるので注意。
参考

宣言時にその型の初期値がゼロ値と同じなら省略すべき

警告
  • should drop = 0 from declaration of var value; it is the zero value
NG
  • var test int = 0
OK
  • var test int
ゼロ値
  • 少なくとも以下の型のゼロ値は覚えておきましょう。

    string
    • ""
    bool
    • false
    interface{}
    • nil (errorもこれ)
    ポインタ
    • nil

    上記以外の intrune 等の数値型は 0 がゼロ値です。

初期値から型が推測できるので、型の指定を省略すべき

警告
  • should omit type int from declaration of var test; it will be inferred from the right-hand side
NG
  • var num int = 1
NG
  • var num = 1
OK
  • num := 1
OK
  • var num rune = 1
info
  • ちなみに rune などの型は値から推測できないので、指定すべきです。

nil を返却することができない

エラー
  • ポインタやインターフェース型以外が返却値として指定された場合は、 nil を返却することはできません。
    • cannot use nil as type SomeStruct in return argument
対応
  • 構造体の場合は値を指定せずに返却すれば、フィールドには各型のゼロ値が格納されます
NG
  • type SomeStruct struct { Id integer Name string } func Function() SomeStruct { return nil }
OK
  • type SomeStruct struct { Id integer Name string } func Function() SomeStruct { return SomeStruct{} }

== の比較演算子で異なる型の値を比較した

エラー
  • 例えば、数値と nil を比較するとエラーになります。
    • invalid operation: val == nil (mismatched types string and nil)
NG
  • var val int if val == nil { print(val) }
OK
  • var val int if val == 0 { print(val) }

integer や string のようなプリミティブな変数は定義した時点で各型のゼロ値で初期化されます。 数値の場合は 0 、文字列の場合は ""(空文字) がゼロ値です。

文字列の数値変換をint()で行った

文字列からの数値変換は strconv.Atoi 関数で行います。

エラー
  • int 関数では文字列を数値に変換することはできません
    • cannot convert "123" (type untyped string) to type int
  • 明示的な変換以外にも、チャネルと異なる型を送信して自動変換された場合にも同じエラーが発生することがあります
NG
  • i := int("123")
OK
  • i, _ := strconv.Atoi("123")
NG
  • ch := make(chan int) ch <- "abc"
OK
  • ch := make(chan int) ch <- 1000

宣言済みの変数に異なる型の値を代入しようとした

エラー
  • 変数の型と異なる値を代入することはできません
    • cannot use "abc" (type string) as type int in assignment
対応
  • 変数の型を合わせましょう。
NG
  • var i int i = "abc"
OK
  • var i string i = "abc"
OK
  • var i int i = 123

ポインタ型でない変数に ポインタとしてアクセスした

エラー
  • アドレスで指定されるべき引数に * をつけてアクセスするとエラーになります。
    • invalid operation: cannot indirect a (variable of type int)
    • invalid indirect of a (type int)
対応
  • 変数をポインタとして渡してあげる場合は & をつけてアドレスを取得しましょう。
NG
  • a := 1 fmt.Println(*a)
OK
  • b := 1 a := &b fmt.Println(*a)
OK
  • a := &[]int{1, 2} fmt.Println(*a)

構造体を引数とする場合は省メモリの観点からポインタ型で受け取ることが多いので、 最後の例のように初期化と同時にアドレスを取得するような書き方もできます。

パッケージ関連

循環インポートは許可されていない

エラー
  • Go ではパッケージ同士が参照し合うようなインポートは許可されていません。
    • import cycle not allowed
対応
  • 参照方向が一方向になるように移動したり、同一パッケージ内に入れて参照するなどの対応が考えられます。
NG
    • a package
    • b package
OK
    • a package
    • b package

インポート対象のパスが不正

エラー
  • インポートするパスがスラッシュで終わっているとエラーになります
    • non-canonical import path "note.crohaco.net/packagename/" (should be "note.crohaco.net/packagename")
NG
  • import "note.crohaco.net/packagename/"
OK
  • import "note.crohaco.net/packagename"

パッケージパスの先頭にドット(.)が含まれていない

エラー
  • インポートに指定したパスの先頭にドットが含まれていない(ドメインでない)というエラーです。
    • imports hello/world: malformed module path "hello/world": missing dot in first path element
NG
  • (GOPATHモードの状態で)
    import "hello/world"
OK
  • import "hello.com/world"
OK
  • (go.modmodule hello が指定されている状態で)
    import "hello/world"
info
  • ただし、 モジュールモード が有効な場合、 go.mod で指定したモジュール名と一致していればエラーにはなりません。
  • 逆に言えばモジュールモードが有効でこのエラーが出る場合、 go.modのモジュール名と一致していないことが考えられます。

package 文にダブルクォートを指定した

エラー
  • package の指定にダブルクォートは必要ありません。
    • expected 'IDENT', found "main"
NG
  • package "main"
OK
  • package main

インポートしたパッケージが存在しない

エラー
  • 当然ですが存在しないパッケージはインポートできません
    • cannot find package "aaaaa" in any of:
対応
  • ローカルからインポートする場合は、カレントディレクトリからのパス (./) を記述しましょう。
NG
  • import "invalidPackage"
OK
  • import "validPackage"
OK
  • import "./local/path/to/package"

package文が書かれていない

エラー
  • Goのファイルは必ず一つのパッケージに属するためpackage文がないとエラーになります
    • expected 'package', found 'EOF'
    • expected 'package', found 'import'
NG
  • import "fmt"
OK
  • package main import "fmt"

import文に文字列以外が指定されている

エラー
  • import文に文字列が指定されていないとエラーになります
    • expected 'STRING', found newline
NG
  • import fmt
OK
  • import "fmt"

main以外のパッケージは実行できない

エラー
  • go run test.go のようにファイルを実行するとき、 test.go が main パッケージに属していなければエラーになります。
    • cannot run non-main package
    • package test; expected main
NG
  • package test
OK
  • package main
info
  • また処理の起点となる main 存在しない場合もエラーになります。
    • runtime.main_main·f: function main is undeclared in the main package

同じディレクトリ内に異なるパッケージに属したファイルが存在する

エラー
  • Golang のインポートはディレクトリ単位で行われます。 そのため、同一ディレクトリ内に異なるパッケージに属するファイルが存在するとエラーになります。
    • found packages test1 (test1.go) and test2 (test2.go) in /path/to/test
NG
    • test/test1.go
    • test/test2.go
    • package test1
    • package test2
OK
    • test/test1.go
    • test/test2.go
    • package test
    • package test
info
  • パッケージ名は統一さえされていれば、ディレクトリ名と一致していなくても構いません

同じ名前のパッケージを再インポートした

エラー
  • 同じ名前のパッケージを再度インポートするとエラーになります
    • test redeclared as imported package name
再現
  • 同じパッケージ名を持つディレクトリがあると: 例えばこのように同じパッケージ名をもつ異なるディレクトリがあるとき

    • test1/test.go
    • test2/test.go
    • package test func Test() { println("test1") }
    • package test func Test() { println("test2") }

    両方を普通にインポートするとエラーとなります

対応
  • 別名を付けてインポートしてあげましょう。
NG
  • import ( "./test1" "./test2" )
OK
  • import ( "./test1" test2 "./test2" )

ゴルーチン関連

go文で関数を呼び出していない

エラー
  • go文に呼び出されていない関数を指定すると上記のエラーが発生します。
    • function must be invoked in go statement
NG
  • go func() {}
OK
  • go func() {}()

チャネルの型と違う値を送信しようとした

エラー
  • チャネルの型と違う値を送信することはできません
    • cannot use i (type string) as type int in send
NG
  • ch := make(chan int) ch <- "abc"
OK
  • ch := make(chan int) ch <- 1000

チャネルから送信したデータを受け取るべき goroutine が存在しない

(多分)パニック
  • チャネルからデータを送信するとき、送信側の処理がブロックされ続けてしまうのを防止するランタイムエラーのようです。
    • all goroutines are asleep - deadlock!
再現
  • 以下のようなチャネルとチャネルを引数に取るデータ受け取り用の関数があるとします。

  • var ch chan int = make(chan int) func receive(ch <-chan int) { fmt.Println(<-ch) fmt.Println("received") }
  • この関数がチャネルから送信したデータを受け取ってくれないと送信元はいつまで経っても処理を再開できません。 つまり、 receive 関数がゴルーチンとしてデータを待ち受けているかどうかがエラー発生を左右します。

NG
  • func main() { ch <- 123 }
OK
  • func main() { go receive(ch) ch <- 123 }
info
  • ただし、チャネルからデータを受け取るかどうかに関わらず 一つ以上動作しているゴルーチンがあるとこのランタイムエラーは発生しません。

その他

分類できなかった子達です

参照しようとした変数等が定義されていない

エラー
  • 参照しようとしている変数やパッケージ定義されていないとエラーが発生します
    • undefined: value
    • undefined: fmt
対応
  • 「定義されていないから代入文で宣言しようとしたら発生した」というケースも多いでしょう。
  • = を使った代入は既存変数への代入を意味します。
  • ローカル変数の宣言は := を使いましょう。
NG
  • value = 1
OK
  • value := 1
info
  • 定義しているつもりでも 参照されるスコープから外れている場合、 このエラーが発生することがあります。
NG
  • if test == 1 { value := 1 } print(value) // undefined: value
NG
  • if value, ok := dict[key]; !ok { // この書き方だと value は if 文の中でのみ有効 value = 1 } print(value) // undefined: value
OK
  • value, ok := dict[key] if !ok { value = 1 } print(value)

if文に等値条件ではなく代入文を指定した

エラー
  • 主にタイポで == ではなく = を使って比較するとこうなります
    • syntax error: assignment a = 0 used as value
      • expected boolean expression, found assignment (missing parentheses around composite literal?)
NG
  • if a = 1 { }
OK
  • if a == 1 { }
info
  • Golangの if文は条件に真偽値のみを指定できます。 また、代入文も値を返却しないのでこのような不正な構文があるとエラーになります。

プログラムの構文として不正な文字列が含まれている

エラー
  • これはGoに限った話ではないんですが、プログラムの制御に関係ない文字が入っていると構文エラーになります
    • invalid character U+0008
info
  • Mac の場合 0x08 という不可視の制御文字が入力されてしまうことがあります。(Slackとかでも問題になりましたね..
  • Macを使う場合はエディタの設定などで乗り切りましょう。もしくはOSを変えるなど。
参考

定数に再代入しようとした

エラー
  • const で定義した定数に対して再び値を割り当てようとするとエラーになります。
    • cannot assign to val
対応
  • 再代入が予想される場合は var で変数を定義しましょう。
NG
  • const val = 1 func Function () { val = 2 }
OK
  • var val = 1 func Function () { val = 2 }

:= の左辺に定義済みの変数が指定されている

エラー
  • := の左側に新しい変数が定義されていないとエラーになります。
    • no new variables on left side of :=
対応
  • 既存変数への代入は = で行うようにしましょう。
  • value, err := f() のように多値が返却される場合、 valueerr のいずれか片方が新規の変数である必要があります。
NG
  • value := Function() value := Function2()
OK
  • value := Function() value = Function2()
NG
  • value, err := Function() value, err := Function2()
OK
  • value, err := Function() value2, err := Function2()
info
  • _ (ブランク識別子)のみ値を代入しようとすると同じエラーが発生するようです

    func Function() { _ := 1 }

_ 変数に代入された値は使用できない

エラー
  • cannot use _ as value
NG
  • value, _ := Function() print(_)
OK
  • value, test := Function() print(test)

使われていないローカル変数やパッケージがある

エラー
  • value declared and not used
    • imported but not used ...
対応
  • 使わない変数は _ (ブランク識別子)に代入しましょう。
NG
  • value, err := Function()
OK
  • value, _ := Function()
OK
  • import _ "./statik"
info
  • 最後の例は blank import というらしいです。
  • 実行はされてほしいけどそのファイル内でパッケージが使われない場合に使います。
参考
info
  • 評価結果が使われない場合もエラーになります。
  • 1 == 1 evaluated but not used
  • 普通こんなことはしませんが、宣言部分だけを削除すると 比較部分だけが取り残されてこのようなエラーが発現することがあります。

関数や構造体の定義が複数行にまたがる場合、行末がカンマか括弧で終わっていない

エラー
  • 構造体の初期化や関数の引数指定で各要素を改行で区切るとき、最終要素が改行で終わっているとこのエラーが発生します。
    • syntax error: unexpected newline, expecting comma or )
    • syntax error: unexpected newline, expecting comma or }
    • missing ',' before newline in composite literal
対応
  • カンマを追加するか、行末を括弧で終わらせるようにします。
NG
  • arr := [] int { 1, 2 }
NG
  • arr := [] int { 1, 2, }
OK
  • arr := [] int { 1, 2}
OK
  • test( 1, 2 )
OK
  • test( 1, 2, )
OK
  • test( 1, 2)
info
  • カンマ以外にも + 等の中置き演算子が行末にある場合は継続行と判断されるようです
参考

ポインタ型変数にnilが入っている状態で参照しようとした

パニック
  • runtime error: invalid memory address or nil pointer dereference
NG
  • var a *int fmt.Println(*a)
NG
  • var a *int *a = 1
OK
  • var a *int b := 1 a = &b // 以下のように値のアドレスを直接参照することはできない a = &1 // cannot take the address of 1 (untyped int constant) a = &nil // cannot take address of nil (untyped nil value)
OK
  • var b *int = nil // 同じポインタ型はそのまま格納できる a = b // ポインタ型の変数のアドレスは型が異なるため格納できない a = &b // cannot use &b (value of type **int) as *int value in assignment

スライスののインデックスに負値を指定した

エラー
  • Pythonのようにスライスの後ろの方から要素を取得するときにマイナスのインデックスを使うことはできません。
    • invalid slice index -1 (index must be non-negative)
対応
  • どうしても後ろからのインデックスを指定する場合には スライスの要素数からマイナスして要素のインデックスを求めましょう。
NG
  • a := []int{1, 2, 3} fmt.Println(a[-1])
OK
  • a := []int{1, 2, 3} fmt.Println(len(a)-1)

スライスの要素数以上のインデックスを参照した

パニック
  • 要素数以上のインデックスを指定して要素を取得しようとするとパニックが発生します。
    • runtime error: index out of range [3] with length 3
対応
  • 要素数未満のインデックスを指定するか、固定のインデックスを指定するときは要素数のチェックをしましょう。
NG
  • a := []int{1, 2, 3} fmt.Println(a[3])
OK
  • a := []int{1, 2, 3} fmt.Println(a[2])

range文で得られる2番めの値が使われていないので省略するべき

警告
  • should omit 2nd value from range; this loop is equivalent to for serial := range
NG
  • for index, _ := range values { }
OK
  • for index := range values { }