22
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

func経由でinterface型にnilを入れると、nil扱いにならない。

Last updated at Posted at 2014-11-01
  • interfaceを実装している構造体を参照で返すfuncを作る
  • そのfuncからnilを返す
  • 実装している構造体参照型として受け取ると、正常にnil判定される
  • interface型で受け取ると、nil判定がうまく行かず、使うともちろんpanicになる

自前でerrorを作ろうとしてたときにハマりました。interfaceに対する理解が足りないのか、バグなのか。。。

interface型は、typeとvalueを持っていて、両方nilでないとnil判定されない

@yosssiさんにコメントで教えていただきました。ありがとうございます。
interface型は、内部的にtype, valueを持っており、nilと判定されるには、そのどちらもnilでないといけないそうです。

https://golang.org/doc/faq#nil_error での説明 "An interface value is nil only if the inner value and type are both unset, (nil, nil)." によりますと、interface型の変数がnilと判定されるには、その内部の値と型の両方がnilである必要があるみたいですね。

今回の例の場合は、errの内部の値がnilである一方で、型がnilでない(*MyErrorである)ために、nilと判定されていないようです。

http://play.golang.org/p/7FIqaGQ9mo

今回のケースでは、受け取る方が*MyErrorの時は、自前の構造体型なので、そのままnilが入り、ちゃんとnil判定されています。
受け取る方が、errorの場合は、interface型なので、上記に該当し、type=*MyError, value=nilという状態になり、nil判定が想定通りにいかないということです。

いただいたコメント内のGo Playgroundでreflectを使って確認されているのでわかりやすいと思います。

また、半年前ぐらいに書かれていた絶対ハマる、不思議なnilにも、詳しく載っていました。

上記のコメントにある通り、明示的にnilを返すというのが大事そうです。

私の場合、最初に変数を宣言して、最後にreturnするやり方は、コードの見通しが良くないと思っているので、エラーがない時はreturn nilといった風に返しています。ただ、複数階層funcを呼ぶコードを書いていて、以下のような直接return OtherFunc()をしている箇所があり、これが、type=*MyError, value=nilという状況を発生させていました。

func TopFunc() error {
	if something {
		// ここで、*MyError型のnilがerrorとして返されてハマる。
		return OtherFunc()
	} else {
		return nil
	}
}

func OtherFunc() *MyError {
	if something {
		return nil
	} else {
		return &MyError{}
	}
}

環境

環境は、golang 1.3.3/MacOS 10.9.5です。
以下のGo Playgroundでも発生します。
http://play.golang.org/p/Jho3HHYTHy

package main

import (
    "fmt"
)

func NoError() error {
    return nil
}

type MyError struct {
    MyMsg string
}

func (e MyError) Error() string {
    return e.MyMsg
}

func MyErrorRef() *MyError {
    return nil
}

func main() {
    var err error
    err = nil
    if err != nil {
        fmt.Printf("1. this message does not show.\n")
    }
    
    err = NoError()
    if err != nil {
        fmt.Printf("2. this message does not show.\n")
    }

    myErr := MyErrorRef()
    if myErr != nil {
        fmt.Printf("3. this message does not show.\n")
    }

    err = MyErrorRef()
    fmt.Printf("4. err: %v, nil: %v\n", err, nil)
    if err != nil {
        fmt.Printf("5. err is nil, but 'err != nil' is true...? will be panic.\n")
        fmt.Printf("6. %s\n", err.Error())
    } else {
        fmt.Printf("7. why doesn't show this message?\n")
    }
}
22
17
3

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
22
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?