web-dev-qa-db-ja.com

nil値を非表示にし、ここでgolangが失敗する理由を理解する

この場合、何かが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であるかどうかを確認する唯一の方法は、リフレクションを使用することです。

これは本当に望んでいた行動ですか?または、コードに重大な間違いが見られないのですか?

リンクをここで再生

44
sharpner

ここでの問題は、showerinterface型であることです。 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値をスライスに入れると、インターフェース値itselfnilになり、条件x == niltrue

デモ

この例を参照してください: 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ではありません。

59
icza

インターフェイスをポインターと考えてみましょう。

ポインタ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を意味します)、ba(1000000)のアドレスを指します。

同じことがインターフェイスにも適用されます(ただし、外観が少し異なります メモリ内 )。

ポインタのように、nilポインタを指すインターフェイスはそれ自体nilではありません

ここでは、 これがポインターでどのように機能するか および インターフェイスでどのように機能するか を確認してください。

8
Moshe Revah