golang: nil ポインターと nil インターフェースの違いを理解する

WBOY
リリース: 2024-07-18 08:51:19
オリジナル
632 人が閲覧しました

golang: Understanding the difference between nil pointers and nil interfaces

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 ポインターをチェックしています。そして、「それは真実ではありません」と表示されるようになります。ここですべてのコードが動作しているのが確認できます。

ボーナス: リフレクションを使用して両方の 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 サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!