1
1

More than 3 years have passed since last update.

Goで関数の引数でnilを指定したときにハマった話

Last updated at Posted at 2020-11-16

前置き

goのnilの話は n番煎じだと思いますが、ハマってしまった記念に記事を書きます..

実際のコードはもうちょっと長くてフレームワークの中だったりするので、原因の特定が大変でしたが、エッセンスだけ抜き出したものを書きます。

このようなメソッドを作りました(失敗例)

ちなみに、テストコードで何度もHTTPRequestを投げるのでラッパー関数のつもりでした。

func Request(method, url string, body *strings.Reader) error {
    request, err := http.NewRequest(method, url, body)
    if err != nil {
        return err
    }
    request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    client := new(http.Client)
    resp, _ := client.Do(request)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(byteArray))
    return nil
}

このメソッドをこのように使うことを想定しています。


func main() {

    values := url.Values{
        "hoge": []string{"fuga"},
    }

    err := Request("POST", "https://google.com", strings.NewReader(values.Encode()))
    if err != nil {
        log.Fatal(err)
    }

}

Requestの第3引数は、 雑に 特に何も考えずにstrings.NewReader の戻り型をそのまま指定しました。

nilを指定する

さて、POSTで何もパラメータを送るものがないとき


    err := Request("POST", "https://google.com", nil)
    if err != nil {
        log.Fatal(err)
    }

と指定するわけなのですが、これを実行すると

実行時エラーでクラッシュします。

strings.(*Reader).Len(...)
        /usr/local/Cellar/go/1.15.3/libexec/src/strings/reader.go:26
net/http.NewRequestWithContext(0x1314620, 0xc00001a0b0, 0x12baeec, 0x3, 0x12be7e1, 0x12, 0x13102c0, 0x0, 0x121018d, 0x0, ...)
        /usr/local/Cellar/go/1.15.3/libexec/src/net/http/request.go:889 +0x2a4
net/http.NewRequest(...)
        /usr/local/Cellar/go/1.15.3/libexec/src/net/http/request.go:813
main.Request(0x12baeec, 0x3, 0x12be7e1, 0x12, 0x0, 0x0, 0x0)

ちなみに、直接 http.NewRequest の第3引数にnilを指定したら大丈夫です。

    req, err := http.NewRequest("POST", "https://google.com", nil)
    if err != nil {
        log.Fatal(err)
    }

エラーの理由は?

IntelliJ IDEA でステップ実行していくとわかりますが、

if body != nil {

Go の nil は型がついているセマンティクスですので、 *strings.NewReaderの型を持ったまま 関数内部に入ってしまい、下のコード時で body自体はnilであるのに
body 中身自体の型は *strings.NewReader で、bodyの宣言は io.Readerですので
nil判定的にはtrueになり (意図せず?) if の中に入ってしまいエラーになっているということになります。

参考:
https://qiita.com/umisama/items/e215d49138e949d7f805

関数の引数でnilを指定すると、関数宣言の引数の型になる

req, err := http.NewRequest("POST", "https://google.com", nil)

さてここで指定された nil ですが、 NewRequest の定義から
io.Reader の nil になります。 (io.Reader が interface でも型とされます)

どうすればよかったか

func Request(method, url string, body *strings.Reader)
の引数を
func Request(method, url string, body io.Reader)

としていればよかったので、テストで使い捨ての関数とはいえ、
関数の引数は、その呼び出す方の型ではなく、中で使っている関数の型に合わせるのが無難でした。

結論

http.NewRequest の第3引数で nilを指定するときは io.Readerの型になるように指定する。

関数の引数は、なるべく中で呼ばれる型に寄せて宣言する。

nilはハマるとよく言われますが、実際にこういうハマりをしないと気づけないものだと思いました..

1
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1