在 Go 語(yǔ)言中,有時(shí) nil 并不是一個(gè) nil
今天,我遇到了一個(gè) Go FAQ[1]。首先,作為一個(gè)小小的 Go 語(yǔ)言測(cè)驗(yàn),看看您是否在 Go playground 中運(yùn)行該程序之前就能推斷出它應(yīng)該打印出的內(nèi)容(我已經(jīng)將程序放在側(cè)邊欄中,以防它在 Go playground 上消失)。該程序的關(guān)鍵代碼是:
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 語(yǔ)言中的變量是使用它們的零值顯式創(chuàng)建的,在指針的情況下,例如 lp 將會(huì)是 nil,您可能期待上述代碼會(huì)正常運(yùn)行(即不執(zhí)行任何操作)。實(shí)際上,它會(huì)在對(duì) fred() 的第二次調(diào)用時(shí)崩潰。原因是,在 Go 語(yǔ)言中,有時(shí)以 nil 為值的變量,如果直接打印的話,它雖然看起來(lái)像 nil,但實(shí)際上并不是真的 nil 。簡(jiǎn)而言之,Go 語(yǔ)言區(qū)別對(duì)待 nil 接口值和轉(zhuǎn)換為接口的值為 nil 的具體類型。只有前者確實(shí)為 nil,因此與字面上的 ni l 相等,就像 fred() 在這里做的一樣。
(因此,可以使用 nil f 調(diào)用 (f *fake) 上的具體方法。它也許是一個(gè) nil 指針,但是它是類型化的 nil 指針,所以可以擁有有方法。甚至在接口轉(zhuǎn)換后依然可以擁有方法,正如上述的例子。)
對(duì)于這里的情況,其解決方法是更改初始化的過(guò)程。實(shí)際的程序條件性地設(shè)置了 fake,類似于下面的代碼:
var?l?*sLogger
if?smtplog?!=?nil?{
????l?=?&sLogger
????l.prefix?=?logpref
????l.writer?=?bufio.NewWriterSize(smtplog,?4096)
}
convo?=?smtpd.NewConvo(conn,?l)
這會(huì)將具體類型為 *sLogger 的 nil 傳遞給期望參數(shù)為 io.Writer 的對(duì)象,從而導(dǎo)致接口轉(zhuǎn)換并掩蓋了 nil。為了解決這個(gè)問(wèn)題,我們可以添加一個(gè)必須顯式設(shè)置的中間變量 io.Writer:
var?l2?io.Writer
if?smtplog?!=?nil?{
????l?:=?&sLogger
????l.prefix?=?logpref
????l.writer?=?....
????l2?=?l
}
convo?=?smtpd.NewConvo(conn,?l2)
如果我們不初始化這個(gè)特殊的日志記錄器 sLogger,則 l2 會(huì)是一個(gè)真正的 io.Writer nil,并會(huì)在 smtpd 包中被檢測(cè)到。
(您可以將類似的初始化操作封裝進(jìn)一個(gè)返回類型為 io.Writer 的函數(shù)中,并在沒(méi)有提供日志記錄器的情況下顯式返回 nil,通過(guò)這樣的技巧來(lái)達(dá)到類似的效果。需要強(qiáng)調(diào)的一點(diǎn)是,函數(shù)必須返回接口類型,如果返回類型為 *sLogger,那么您將再次遇到相同的問(wèn)題。)
在 sLogger 的方法中保留對(duì)零值的防護(hù)代碼,這是一個(gè)個(gè)人喜好問(wèn)題。然而,我不想這么做,如果將來(lái)我在代碼中遇到類似的初始化錯(cuò)誤,我希望它崩潰,以便對(duì)其進(jìn)行修復(fù)。
我從這件事中學(xué)到的另一個(gè)教訓(xùn)是,如果是出于調(diào)試的目的而進(jìn)行的打印,我不會(huì)再使用 %v 作為格式說(shuō)明符,而會(huì)使用 %#v。因?yàn)榍罢邔?huì)為接口 nil 和具體類型的 nil 同樣打印一個(gè)普通且具有誤導(dǎo)性的 ,而 %#v 將為前者打印出 ,為后者打印 (*main.fake)(nil) 。
邊注欄: 測(cè)試程序
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[2]譯者:anxk[3]校對(duì):polaris1119[4]
本文由 GCTT[5] 原創(chuàng)編譯,Go 中文網(wǎng)[6] 榮譽(yù)推出
參考資料
Go FAQ: http://golang.org/doc/faq#nil_error
[2]ChrisSiebenmann: https://utcc.utoronto.ca/~cks/space/People/ChrisSiebenmann
[3]anxk: https://github.com/anxk
[4]polaris1119: https://github.com/polaris1119
[5]GCTT: https://github.com/studygolang/GCTT
[6]Go 中文網(wǎng): https://studygolang.com/
推薦閱讀
xxx
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語(yǔ)言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛(ài)好者值得關(guān)注
