[Go] Typed Nil

Cyan Ho
4 min readMay 6, 2021

最近在改版我的開源套件,寫測試時偶然發現 typed nil 這個東西,紀錄一下踩到的小雷。

我寫測試都是使用 testify 這個套件,今天在針對一個 nil 場景做測試時,忽然發現一個意料之外的結果,我以簡單的程式碼來表達我所遇到的問題:

上圖中 TestTypedNil 這個測試會失敗,會得到 <nil> != (*int)(nil) 的錯誤。讓這個測試通過有幾種方法:

第一種是將 nil 轉成比較目標的指標型別,第二種是使用 testify/suite.Suite 身上的 Nil 方法,如果只是單純想知道結果是不是 nil,第二種方法比較簡單直覺。

發現這個現象後,我就開始好奇,那我們一般在判斷指標是否有值時,會不會也有這個問題?

經過測試實驗之後,原來等號判斷會幫我們處理掉型別不一致的問題,所以之前我都沒有注意到有 typed nil 的存在。

所以 typed nil 跟一般的 nil (untyped nil) 差別在哪裡呢?關於這個問題我有查到一個討論串,裡面有一些例子試著解釋為何 typed nil 需要存在(安全性、明確性):

還延伸到官方文件的 FAQ 其中一題 Why is my nil error value not equal to nil?:

大致上了解到的是,Go 語言的變數包含兩種資訊:型別(Type)與值(Value)。Untyped nil 就相當於一個值為 nil、型別為 nil 的東西,而本文測試例子中的變數 i 相當於一個值為 nil、型別為 *int 的東西,所以本質上這兩個 nil 代表的意義不同。

上面的 FAQ 還延伸出另一個問題,當 concrete type 的東西被轉成 Interface 後,會產生出不一樣的結果,如下例:

當值為 nil 的 *MyError 被轉成 Interface 後(var ie interface{} = e),會變成 (T=*MyError, V=nil) 的 Interface(請參考上面 FAQ 連結),而等號判斷發現左邊的 ie 型別為 Interface 後,會將右邊的 nil 當作 (T=nil, V is not set) 的 Interface,因此兩者比較的結果為不相等。

因為一個意外所以發現了 typed nil 這個東西,以後在做型別宣告、轉換或是變數比較時,會更小心謹慎些,在寫測試時遇到這種狀況也會比較知道發生了什麼事。

--

--