Go で nil がどのように機能するか、そして、何かが同時に nil であると同時に nil ではない場合があることについて少し考えていました。
ここに、nil ポインターにはなり得るが、nil インターフェイスにはなり得ないものの小さな例を示します。それが何を意味するのか見ていきましょう。
まず、go にはインターフェースの概念があり、これは一部のオブジェクト指向言語のインターフェースと似ていますが、まったく同じではありません (ほとんどの定義では go は OOP ではありません)。 Go では、インターフェイスは、インターフェイスを満たすために別の型が実装する必要がある関数を定義する型です。これにより、さまざまな方法でインターフェイスを満たすことができる複数の具象型を使用できるようになります。
たとえば、error は 1 つのメソッドを持つ組み込みインターフェイスです。次のようになります:
type error interface { Error() string }
エラーとして使用したい型には、文字列を返す Error というメソッドが必要です。たとえば、次のコードを使用できます:
type ErrorMessage string func (em ErrorMessage) Error() string { return string(em) } func DoSomething() error { // Try to do something, and it fails. if somethingFailed { var err ErrorMessage = "This failed" return err } return nil } func main() { err := DoSomething() if err != nil { panic(err) } }
この例では、何か問題が発生すると DoSomething がエラーを返すことに注意してください。 ErrorMessage タイプを使用できます。これは、文字列を返す Error 関数があり、エラー インターフェイスを実装しているためです。
エラーが発生しなかった場合は、nil を返します。
go では、ポインターは値を指しますが、値を指さないこともできます。その場合、ポインターは nil になります。例:
var i *int = nil func main() { if i == nil { j := 5 i = &j } fmt.Println("i is", *i) }
この場合、変数 i は int へのポインタです。 int を作成してそれを指すまでは、nil ポインターとして始まります。
ユーザー定義型には関数 (メソッド) を付けることができるため、型へのポインターの関数も持つことができます。これは Go では非常に一般的な方法です。これは、ポインタもインターフェイスを実装できることを意味します。このようにして、非 nil インターフェースであるが、依然として nil ポインターである値を持つことができます。次のコードを考えてみましょう:
type TruthGetter interface { IsTrue() bool } func PrintIfTrue(tg TruthGetter) { if tg == nil { fmt.Println("I can't tell if it's true") return } if tg.IsTrue() { fmt.Println("It's true") } else { fmt.Println("It's not true") } }
IsTrue() bool メソッドを持つ型はすべて PrintIfTrue に渡すことができますが、nil も同様に渡すことができます。したがって、PrintIfTrue(nil) を実行すると、「true かどうかはわかりません」と表示されます。
次のような簡単なこともできます:
type Truthy bool func (ty Truthy) IsTrue() bool { return bool(ty) } func main() { var ty Truthy = true PrintIfTrue(ty) }
これにより、「It's true」と表示されます。
または、次のようなもっと複雑なこともできます。
type TruthyNumber int func (tn TruthyNumber) IsTrue() bool { return tn > 0 } func main() { var tn TruthyNumber = -4 PrintIfTrue(tn) }
「それは真実ではありません」と表示されます。これらの例はどちらもポインターではないため、これらの型のいずれでも nil になる可能性はありませんが、次の点を考慮してください。
type TruthyPerson struct { FirstName string LastName string } func (tp *TruthyPerson) IsTrue() bool { return tp.FirstName != "" && tp.LastName != "" }
この場合、Truthyperson は TruthGetter を実装しませんが、*Truthyperson は実装します。したがって、これは機能するはずです:
func main() { tp := &TruthyPerson{"Jon", "Grady"} PrintIfTrue(tp) }
これが機能するのは、tp が Truthyperson へのポインタであるためです。ただし、ポインタが nil の場合はパニックになります。
func main() { var tp *TruthyPerson PrintIfTrue(tp) }
これではパニックになります。ただし、PrintIfTrue ではパニックは発生しません。 PrintIfTrue は nil をチェックするので、これで問題ないと思うかもしれません。しかし、ここで問題です。 TruthGetter に対して nil をチェックしています。言い換えれば、nil インターフェイスはチェックしますが、nil ポインタはチェックしません。また、 func (tp *Truthyperson) IsTrue() bool では、nil をチェックしません。 Go では、引き続き nil ポインターでメソッドを呼び出すことができるため、そこでパニックが発生します。修正は実際には非常に簡単です。
func (tp *TruthyPerson) IsTrue() bool { if tp == nil { return false } return tp.FirstName != "" && tp.LastName != "" }
今、PrintIfTrue の nil インターフェースと func (tp *Truthyperson) IsTrue() bool の nil ポインターをチェックしています。そして、「それは真実ではありません」と表示されるようになります。ここですべてのコードが動作しているのが確認できます。
リフレクションを使用すると、PrintIfTrue に小さな変更を加えて、nil インターフェースと nil ポインターの両方をチェックできるようになります。コードは次のとおりです:
func PrintIfTrue(tg TruthGetter) { if tg == nil { fmt.Println("I can't tell if it's true") return } val := reflect.ValueOf(tg) k := val.Kind() if (k == reflect.Pointer || k == reflect.Chan || k == reflect.Func || k == reflect.Map || k == reflect.Slice) && val.IsNil() { fmt.Println("I can't tell if it's true") return } if tg.IsTrue() { fmt.Println("It's true") } else { fmt.Println("It's not true") } }
ここでは、前と同様に、最初に nil インターフェースをチェックします。次に、リフレクションを使用して型を取得します。ポインタに加えて、chan、func、map、slice も nil になる可能性があるため、値がそれらの型のいずれかであるかどうかを確認し、そうであればそれが nil であるかどうかを確認します。本当の場合は、「本当かどうかはわかりません」というメッセージも返します。これはあなたが望んでいることと全く同じかもしれませんが、オプションです。この変更により、次のことが可能になります:
func main() { var tp *TruthyPerson PrintIfTrue(tp) }
次のような、より単純なものへの提案が表示される場合があります。
// Don't do this if tg == nil && reflect.ValueOf(tg).IsNil() { fmt.Println("I can't tell if it's true") return }
これがうまく機能しない理由は 2 つあります。まず、リフレクションを使用するとパフォーマンスのオーバーヘッドが発生するということです。リフレクションの使用を避けられるのであれば、そうすべきでしょう。最初に nil インターフェースをチェックすると、それが nil インターフェースであればリフレクションを使用する必要はありません。
2 番目の理由は、値の型が nil になれる型ではない場合に、reflect.Value.IsNil() がパニックを起こすことです。そのため、種類のチェックを追加します。 Kind をチェックしていなかったら、Truthy 型と TruthyNumber 型でパニックが起こっていたでしょう。
したがって、最初に種類を確認する限り、「真実ではありません」ではなく「真実かどうかはわかりません」と表示されるようになります。見方によっては、これは改善されるかもしれません。この変更を加えた完全なコードは次のとおりです。
これはもともと Dan's Musings に掲載されたものです
以上がgolang: nil ポインターと nil インターフェースの違いを理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。