首頁 > 後端開發 > Golang > 主體

golang:理解 nil 指標和 nil 介面之間的區別

WBOY
發布: 2024-07-18 08:51:19
原創
639 人瀏覽過

golang: Understanding the difference between nil pointers and nil interfaces

我在思考 nil 在 go 中的不同工作方式,以及有時某些東西可以同時為 nil 和非 nil。

這是一個可以是 nil 指標但不是 nil 介面的小例子。讓我們來看看這意味著什麼。

介面

首先,go 有一個介面的概念,它與一些物件導向語言中的介面類似,但又不完全相同(根據大多數定義,go 不是 OOP)。在 Go 中,介面是一種類型,它定義了另一種類型必須實作才能滿足該介面的函數。這允許我們擁有多種具體類型,可以以不同的方式滿足介面。

例如,error 是一個具有單一方法的內建介面。看起來像這樣:

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 的指標。它一開始是一個 nil 指針,直到我們創建一個 int 並將其指向該指針。

指針和接口

由於使用者定義的類型可以附加函數(方法),因此我們也可以擁有指向類型的指標的函數。這是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) ,它會印出「我無法判斷它是否為真」。

我們也可以做一些簡單的事情,例如這樣:

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 的指標。然而,如果指針為零,我們就會感到恐慌。

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
}
登入後複製

這效果不好有兩個原因。首先,使用反射時會產生效能開銷。如果您可以避免使用反射,那麼您可能應該這樣做。如果我們先檢查 nil 接口,那麼如果它是 nil 接口,我們就不必使用反射。

第二個原因是,如果值的型別不是可以為 nil 的型,reflect.Value.IsNil() 將會發生恐慌。這就是我們添加此類檢查的原因。如果我們沒有檢查 Kind,那麼我們會對 Truthy 和 TruthyNumber 類型感到恐慌。

因此,只要我們確保先檢查類型,現在就會列印“我無法判斷它是否為真”,而不是“這不是真的”。根據您的觀點,這可能是一種改進。這是進行此更改的完整程式碼。

本文原刊於 Dan's Musings

以上是golang:理解 nil 指標和 nil 介面之間的區別的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!