Go 語言中,有時 nil 並不是一個 nil

polarisxu發表於2020-08-10

今天,我遇到了一個 Go FAQ。首先,作為一個小小的 Go 語言測驗,看看您是否在 Go playground 中執行該程式之前就能推斷出它應該列印出的內容(我已經將程式放在側邊欄中,以防它在 Go playground 上消失)。該程式的關鍵程式碼是:

type fake struct { io.Writer }

func fred (logger io.Writer) {
   if logger != nil {
      logger.Write([]byte("..."))
   }
}

func main() {
   var lp *fake
   fred(nil)
   fred(lp)
}

由於 Go 語言中的變數是使用它們的零值顯式建立的,在指標的情況下,例如 lp 將會是 nil,您可能期待上述程式碼會正常執行(即不執行任何操作)。實際上,它會在對 fred() 的第二次呼叫時崩潰。原因是,在 Go 語言中,有時以 nil 為值的變數,如果直接列印的話,它雖然看起來像 nil,但實際上並不是真的 nil 。簡而言之,Go 語言區別對待 nil 介面值和轉換為介面的值為 nil 的具體型別。只有前者確實為 nil,因此與字面上的 ni l 相等,就像 fred() 在這裡做的一樣。

(因此,可以使用 nil f 呼叫 (f *fake) 上的具體方法。它也許是一個 nil 指標,但是它是型別化的 nil 指標,所以可以擁有有方法。甚至在介面轉換後依然可以擁有方法,正如上述的例子。)

對於這裡的情況,其解決方法是更改初始化的過程。實際的程式條件性地設定了 fake,類似於下面的程式碼:

var l *sLogger

if smtplog != nil {
    l = &sLogger
    l.prefix = logpref
    l.writer = bufio.NewWriterSize(smtplog, 4096)
}
convo = smtpd.NewConvo(conn, l)

這會將具體型別為 *sLoggernil 傳遞給期望引數為 io.Writer 的物件,從而導致介面轉換並掩蓋了 nil。為了解決這個問題,我們可以新增一個必須顯式設定的中間變數 io.Writer

var l2 io.Writer

if smtplog != nil {
    l := &sLogger
    l.prefix = logpref
    l.writer = ....
    l2 = l
}
convo = smtpd.NewConvo(conn, l2)

如果我們不初始化這個特殊的日誌記錄器 sLogger,則 l2 會是一個真正的 io.Writer nil,並會在 smtpd 包中被檢測到。

(您可以將類似的初始化操作封裝進一個返回型別為 io.Writer 的函式中,並在沒有提供日誌記錄器的情況下顯式返回 nil,通過這樣的技巧來達到類似的效果。需要強調的一點是,函式必須返回介面型別,如果返回型別為 *sLogger,那麼您將再次遇到相同的問題。)

sLogger 的方法中保留對零值的防護程式碼,這是一個個人喜好問題。然而,我不想這麼做,如果將來我在程式碼中遇到類似的初始化錯誤,我希望它崩潰,以便對其進行修復。

我從這件事中學到的另一個教訓是,如果是出於除錯的目的而進行的列印,我不會再使用 %v 作為格式說明符,而會使用 %#v。因為前者將會為介面 nil 和具體型別的 nil 同樣列印一個普通且具有誤導性的 ,而 `%#v` 將為前者列印出 ,為後者列印 (*main.fake)(nil)

邊注欄: 測試程式

package main

import (
    "fmt"
    "io"
)

type fake struct {
    io.Writer
}

func fred(logger io.Writer) {
    if logger != nil {
        logger.Write([]byte("a test\n"))
    }
}

func main() {
    // 這裡的 t 的值是 nil
    var t *fake

    fred(nil)
    fmt.Printf("passed 1\n")
    fred(t)
    fmt.Printf("passed 2\n")
}

via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoNilNotNil

作者:ChrisSiebenmann 譯者:anxk 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

相關文章