この場合、何かがnil
ではないことを正しく保証する方法を理解できません。
package main
type shower interface {
getWater() []shower
}
type display struct {
SubDisplay *display
}
func (d display) getWater() []shower {
return []shower{display{}, d.SubDisplay}
}
func main() {
// SubDisplay will be initialized with null
s := display{}
// water := []shower{nil}
water := s.getWater()
for _, x := range water {
if x == nil {
panic("everything ok, nil found")
}
//first iteration display{} is not nil and will
//therefore work, on the second iteration
//x is nil, and getWater panics.
x.getWater()
}
}
その値が実際にnil
であるかどうかを確認する唯一の方法は、リフレクションを使用することです。
これは本当に望んでいた行動ですか?または、コードに重大な間違いが見られないのですか?
ここでの問題は、shower
がinterface
型であることです。 Goのインターフェイスタイプは、実際の値とそのdynamicタイプを保持します。これに関する詳細: 反射の法則#インターフェースの表現 。
返されるスライスには、2つの非nil
値が含まれます。 2番目の値はインターフェイス値であり、nil
値と*display
タイプを保持する(値;タイプ)のペアです。 Go言語仕様:比較演算子 :から引用
インターフェイス値は比較可能です。 2つのインターフェイス値は、それらが同じ動的型と同じ動的値を持っている場合、または両方が値
nil
を持っている場合、等しいです。
したがって、それをnil
と比較すると、false
になります。それを(nil;*display)
のペアを表すインターフェイス値と比較すると、true
になります。
if x == (*display)(nil) {
panic("everything ok, nil found")
}
インターフェイスが保持する実際のタイプを知る必要があるため、これは実行不可能と思われます。ただし、 Value.IsNil()
を使用して、リフレクションを使用して、nil
以外のインターフェイス値がnil
値をラップするかどうかを判断できることに注意してください。この例は Go Playground で見ることができます。
なぜこのように実装されているのですか?
他の具象タイプとは異なるインターフェース(非インターフェース)は、異なる具象タイプ(異なる静的タイプ)の値を保持できます。ランタイムは、インターフェイスタイプの変数に格納されている値の動的タイプまたはランタイムタイプを知る必要があります。
interface
は単なるメソッドセットです。任意のタイプは、同じメソッドがタイプの メソッドセット の一部である場合に実装します。 nil
にはできないタイプがあります。たとえば、struct
や、基になるタイプとしてint
を持つカスタムタイプがあります。これらの場合、その特定のタイプのnil
値を保存できる必要はありません。
ただし、任意のタイプには、nil
が有効な値である具体的なタイプ(スライス、マップ、チャンネル、すべてのポインタータイプなど)も含まれるため、値を保存するためにインターフェイスを満たすランタイムでは、nil
をインターフェイス内に格納することをサポートするのが妥当です。ただし、インターフェイス内のnil
に加えて、nil
値にはそのような情報が含まれていないため、動的型を格納する必要があります。代替オプションは、格納される値がnil
である場合、インターフェイス値としてnil
を使用することですが、動的な型情報を失うため、この解決策では不十分です。
Goのインターフェースは動的に型付けされていると言う人もいますが、それは誤解を招く恐れがあります。これらは静的に型付けされます。インターフェイス型の変数は常に同じ静的型を持ち、実行時にインターフェイス変数に格納された値が型を変更しても、その値は常にインターフェイスを満たします。
一般に、nil
型の値にinterface
を指定する場合は、明示的なnil
値を使用してから、nil
の等しいかどうかをテストできます。最も一般的な例は、組み込みの error
タイプで、これは1つのメソッドとのインターフェースです。エラーがない場合は常に、具体的な(インターフェイスではない)タイプのエラー変数の値ではなく、値nil
を明示的に設定または返します(これは実際には悪い習慣です。以下のデモを参照してください)。
あなたの例では、以下の事実から混乱が生じています。
shower
)shower
型ではなく、具象型ですしたがって、*display
型をshower
スライスに入れると、値がnil
と型が*display
である(value; type)のペアであるインターフェイス値が作成されます。ペア内のvalueは、インターフェース値そのものではなく、nil
になります。 nil
値をスライスに入れると、インターフェース値itselfはnil
になり、条件x == nil
はtrue
。
デモ
この例を参照してください: Playground
type MyErr string
func (m MyErr) Error() string {
return "big fail"
}
func doSomething(i int) error {
switch i {
default:
return nil // This is the trivial true case
case 1:
var p *MyErr
return p // This will be false
case 2:
return (*MyErr)(nil) // Same as case 1
case 3:
var err error // Zero value is nil for the interface
return err // This will be true because err is already interface type
case 4:
var p *MyErr
return error(p) // This will be false because the interface points to a
// nil item but is not nil itself.
}
}
func main() {
for i := 0; i <= 4; i++ {
err := doSomething(i)
fmt.Println(i, err, err == nil)
}
}
出力:
0 <nil> true
1 <nil> false
2 <nil> false
3 <nil> true
4 <nil> false
ケース2では、nil
ポインターが返されますが、最初にインターフェイスタイプ(error
)に変換されるため、nil
値とタイプ*MyErr
を保持するインターフェイス値が作成されます。 、したがって、インターフェース値はnil
ではありません。
インターフェイスをポインターと考えてみましょう。
ポインタa
があり、それがnilで、何も指していないとします。
var a *int // nil
次に、ポインタb
があり、a
を指します。
var b **int
b = &a // not nil
何が起こったの? b
は、何も指し示していないポインターを指します。したがって、チェーンの最後にあるnilポインターであっても、b
は何かを指します-nilではありません。
プロセスのメモリを覗くと、次のようになります。
address | name | value
1000000 | a | 0
2000000 | b | 1000000
見る? a
はアドレス0を指し(nil
を意味します)、b
はa
(1000000)のアドレスを指します。
同じことがインターフェイスにも適用されます(ただし、外観が少し異なります メモリ内 )。
ポインタのように、nilポインタを指すインターフェイスはそれ自体nilではありません。
ここでは、 これがポインターでどのように機能するか および インターフェイスでどのように機能するか を確認してください。