nil,看這篇就夠

寫過 Go 代碼的人,肯定對下面的代碼不陌生:

if err != nil {
    //...
}

Go 項目中這行代碼會大量存在,這里可能隱藏著陷阱。

1. Go 中的 nil

Go 中 nil 代表零值,表示什么都沒有,其他語言中也有類似的設計,比如 Java 中的 null。

也不是所有類型的零值都是 nil,Go 中不同類型的零值如下:

Go 中的數(shù)據(jù)類型可以分為基本類型和復合類型。

基本類型的零值都不同,有的是數(shù)字,有的是空字符串,對于復合類型, 零值都是 nil。

零值在有些情況下會讓程序崩潰,比如指針的零值,因為指針是指向一塊內(nèi)存地址,如果指針為 nil,那么就表示不指向任何地址,那么使用這個指針內(nèi)存操作就會出現(xiàn) panic。

還有一點需要注意,nil 并不是關鍵字,nil 在 Go 中是這樣定義的:

var nil Type // Type must be a pointer, channel, func, interface, map, or slice type

nil 的類型只能是指針,channel、函數(shù)、接口和 slice。

也就是說這樣寫代碼是完全合法的:

var nil = make(map[string]string)

但永遠不要這么做,否則程序就完蛋了。

2. nil 中的陷阱

從 nil 的定義可以知道,nil 只能和指針等幾種類型一起使用。

指針的結(jié)構(gòu)相對簡單,就是指向一個內(nèi)存地址,我們可以很安全的使用 nil 來判斷指針是不是為空,有沒有指向內(nèi)存地址。

channel、map、function 和 slice 的本質(zhì)都是在使用指針,所以也可以使用 nil 來判斷這些類型是否初始化、是否能使用。slice 特殊一點,還有 len 和 cap 兩個屬性,但不影響 nil 的判斷:

而 interface 中的 nil,有隱藏的陷阱。

inteface 的接口與上面的類型都不一樣,interface 由兩部分組成,一部分是接口的類型,另一部分是接口的值:

先看下面代碼的輸出:

var in io.Writer
fmt.Printf("%T\n", in)  // nil

var inP *io.Writer
fmt.Printf("%T\n", inP) // *io.Writer

%T 表示輸出這個值的類型。

聲明上面的變量之后,此時 in 和 inP 的結(jié)構(gòu)是下面這樣的:

如果再接著寫下面的代碼:

var in io.Writer
if in != nil {
      in.Write([]byte("logs"))
}

var inP *io.Writer
if inP != nil {
        inP.Write([]byte("logs")) // 這里會發(fā)生 panic
}

inP 的 type 不是 nil,那么 inP 就不等于 nil。在使用接口時要注意,只有接口的類型和值都是 nil 時,這個接口才等于 nil,否則不相等。

錯誤處理是 Go 程序的重要組成部分,但是這里也容易出現(xiàn)陷阱,看如下的代碼:

func main() {
    err := Do()       // nil
    fmt.Printf("result: %+v\n", (err == nil)) // true
}

func Do() *DoError { // nil
    return nil
}

type DoError struct {

}

func (d *DoError) Error() string {
    return "doError"
}

上面的代碼返回的不是接口,而是 DoError 類型的指針,所以判斷是否為 nil 沒問題。

如果換種形式,看下面的代碼,返回的是 error 的接口類型,但是這個接口的類型是 *DoError,值是 nil,這樣一來,就和預期的結(jié)果不符合。

func main() {
    err := Do()       // error(*DoError, nil)
    fmt.Printf("result: %+v\n", (err == nil)) // false
}

func Do() error {    // error(*DoError, nil)
   var err *DoError
   return err      // nil 
}

判斷 err 是不是 nil 是非常高頻的使用場景,在處理這些錯誤時,要非常小心。

3. nil 的其他作用

nil 除了作為零值之外,還有其他的用途。

nil 作為方法的接受者是完全合法的,這里 p 是一個 *Person 類型的 nil:

func main() {
    var p *Person // nil
    p.SayHi()
}

type Person struct {
}

func (p *Person) SayHi() {
    fmt.Println("hi")
}

nil 還可以作為默認值,下面的代碼應該也看的不少了,通常情況下,第二個參數(shù)我們都會直接傳入 nil,這里 nil 的含義是使用默認的配置,我們在自己的代碼中也可以這樣使用。

http.HandleFunc("localhost:8080", nil)

4. 小結(jié)

nil 是指針,channel、函數(shù)、接口和 slice 等類型的零值,其中 interface 的零值有點特殊,只有在類型和值都是 nil 的時候,這個接口才是 nil。

nil 除了作為零值使用之外,還有很多其他的用途,比如作為方法的接受者,表示默認值。

文 / Rayjun

[1] https://www.youtube.com/watch?v=ynoY2xz-F8s

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容