原文鏈接:Dave Cheney的博文
先從一段代碼說起:
package main
import (
"encoding/json"
"fmt"
)
type Result struct {
Foo string
}
func main() {
content := `{"foo": "bar"}`
res := &Result{}
err := json.Unmarshal([]byte(content), &res)
if err != nil {
panic(err)
}
fmt.Printf("res = %+v\n", res) // 正常返回
res2 := &Result{}
err = json.Unmarshal([]byte(content), res2)
if err != nil {
panic(err)
}
fmt.Printf("res2 = %+v\n", res2) // 正常返回
var res3 *Result
err = json.Unmarshal([]byte(content), &res3)
if err != nil {
panic(err)
}
fmt.Printf("res3 = %+v\n", res3) // 正常返回
var res4 *Result
err = json.Unmarshal([]byte(content), res4)
if err != nil {
panic(err)
}
fmt.Printf("res4 = %+v\n", res4) // panic!?。?}
這是一段并不復(fù)雜的代碼:嘗試將一段文本反序列化到一個go 結(jié)構(gòu)體,示例中給出四種定義,其中只有一種情況發(fā)生了報錯,我們接下來就從這個異常情況來簡單說下go語言體系中,空指針的概念。
在上面代碼示例中,發(fā)生報錯的res4和res2從數(shù)據(jù)類型上來說并無不同,都是一個指向Result結(jié)構(gòu)體的指針。我們來簡單看下發(fā)生報錯時我們拿到的提示json: Unmarshal(nil *main.Result)??瓷先ソY(jié)果比較清晰,json.Unmarshal并不接受空指針(nil pointer)。
事實是這樣嗎?我們?nèi)?code>encoding/json的文檔中查看一下:
Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError.(https://pkg.go.dev/encoding/json?tab=doc#Unmarshal
)
果然,文檔完善的標(biāo)準(zhǔn)庫給了我們足夠清晰的結(jié)論:對于非指針和空指針的參數(shù),會拋出InvalidUnmarshalError。
那么,encoding/json的接口又是為什么這么設(shè)計呢?這里我們要先清楚以下兩個前提:
-
go中的賦值操作都是傳值的,這里的賦值包括變量的定義初始化,變量的綁定,函數(shù)參數(shù)傳遞。 -
go的空指針和指向空值的指針有本質(zhì)上區(qū)別:指向空值的指針本身指向的是一個空的對象(取決于指針的類型),被指向的對象本身是一個已經(jīng)分配好的空間;空指針就是指針的空值nil,本身不指向任一對象。
基于以上兩個前提,我們再來看下Unmarshal的函數(shù)定義:
func Unmarshal(data []byte, v interface{}) error
函數(shù)本身的返回值并不包含反序列之后的對象,只有一個描述結(jié)果的error返回值。所以Unmarshal的函數(shù)行為一定是通過改寫v對象來達(dá)到的。根據(jù)第一條前提,我們知道如果v本身是非指針的話,Unmarshal的改寫行為無法影響傳遞進(jìn)來的值對象的原始值;又根據(jù)第二條前提,v如果是nil指針的話,函數(shù)無法根據(jù)指針去改寫它指向的對象。
一個Unmarshal的文檔描述,引出了go語言的兩個知識點,還是挺有趣的。