a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > Golang > 正文

Go語言interface 與 nil 的比較

2020-04-01 19:04:10
字體:
來源:轉載
供稿:網友

interface簡介

Go語言以簡單易上手而著稱,它的語法非常簡單,熟悉C++,Java的開發者只需要很短的時間就可以掌握Go語言的基本用法。

interface是Go語言里所提供的非常重要的特性。一個interface里可以定義一個或者多個函數,例如系統自帶的io.ReadWriter的定義如下所示:

type ReadWriter interface {  Read(b []byte) (n int, err error)  Write(b []byte) (n int, err error)}

任何類型只要它提供了Read和Write的綁定函數實現,Go就認為這個類型實現了這個interface(duck-type),而不像Java需要開發者使用implements標明。

然而Go語言的interface在使用過程中卻有一個特別坑的特性,當你比較一個interface類型的值是否是nil的時候,這是需要特別注意避免的問題。

一次真實的踩坑

這是我們在GoWorld分布式游戲服務器的開發中,碰到的一個實際的bug。由于GoWorld支持多種不同的數據庫(包括MongoDB,Redis等)來保存服務端對象,因此GoWorld在上層提供了一個統一的對象存儲接口定義,而不同的對象數據庫實現只需要實現EntityStorage接口所提供的函數即可。

// EntityStorage defines the interface of entity storage backendstype EntityStorage interface { List(typeName string) ([]common.EntityID, error) Write(typeName string, entityID common.EntityID, data interface{}) error Read(typeName string, entityID common.EntityID) (interface{}, error) Exists(typeName string, entityID common.EntityID) (bool, error) Close() IsEOF(err error) bool}

以一個使用Redis作為對象數據庫的實現為例,函數OpenRedis連接Redis數據庫并最終返回一個redisEntityStorage對象的指針。

// OpenRedis opens redis as entity storagefunc OpenRedis(url string, dbindex int) *redisEntityStorage { c, err := redis.DialURL(url) if err != nil { return nil } if dbindex >= 0 { if _, err := c.Do("SELECT", dbindex); err != nil {  return nil } } es := &redisEntityStorage{ c: c, } return es}

在上層邏輯中,我們使用OpenRedis函數連接Redis數據庫,并將返回的redisEntityStorage指針賦值個一個EntityStorage接口變量,因為redisEntityStorage對象實現了EntityStorage接口所定義的所有函數。

var storageEngine StorageEngine // 這是一個全局變量storageEngine = OpenRedis(cfg.Url, dbindex)if storageEngine != nil {  // 連接成功  ...} else {  // 連接失敗  ...}

上面的代碼看起來都很正常,OpenRedis在連接Redis數據庫失敗的時候會返回nil,然后調用者將返回值和nil進行比較,來判斷是否連接成功。這個就是Go語言少有的幾個深坑之一,因為不管OpenRedis函數是否連接Redis成功,都會運行連接成功的邏輯。

尋找問題所在

想要理解這個問題,首先需要理解interface{}變量的本質。在Go語言中,一個interface{}類型的變量包含了2個指針,一個指針指向值的類型,另外一個指針指向實際的值。 我們可以用如下的測試代碼進行驗證。

// InterfaceStructure 定義了一個interface{}的內部結構type InterfaceStructure struct { pt uintptr // 到值類型的指針 pv uintptr // 到值內容的指針}// asInterfaceStructure 將一個interface{}轉換為InterfaceStructurefunc asInterfaceStructure (i interface{}) InterfaceStructure { return *(*InterfaceStructure)(unsafe.Pointer(&i))}func TestInterfaceStructure(t *testing.T) { var i1, i2 interface{} var v1 int = 0x0AAAAAAAAAAAAAAA var v2 int = 0x0BBBBBBBBBBBBBBB i1 = v1 i2 = v2 fmt.Printf("sizeof interface{} = %d/n", unsafe.Sizeof(i1)) fmt.Printf("i1 %x %+v/n", i1, asInterfaceStructure(i1)) fmt.Printf("i2 %x %+v/n", i2, asInterfaceStructure(i2)) var nilInterface interface{} fmt.Printf("nil interface = %+v/n", asInterfaceStructure(nilInterface))}

這段代碼的輸出如下:

sizeof interface{} = 16i1 aaaaaaaaaaaaaaa {pt:5328736 pv:825741282816}i2 bbbbbbbbbbbbbbb {pt:5328736 pv:825741282824}nil interface = {pt:0 pv:0}

所以對于一個interface{}類型的nil變量來說,它的兩個指針都是0。這是符合Go語言對nil的標準定義的。在Go語言中,nil是零值(Zero Value),而在Java之類的語言里,null實際上是空指針。關于零值和空指針有什么區別,這里就不再展開了。

當我們將一個具體類型的值賦值給一個interface類型的變量的時候,就同時把類型和值都賦值給了interface里的兩個指針。如果這個具體類型的值是nil的話,interface變量依然會存儲對應的類型指針和值指針。

func TestAssignInterfaceNil(t *testing.T) { var p *int = nil var i interface{} = p fmt.Printf("%v %+v is nil %v/n", i, asInterfaceStructure(i), i == nil)}

輸入如下:

<nil> {pt:5300576 pv:0} is nil false

可見,在這種情況下,雖然我們把一個nil值賦值給interface{},但是實際上interface里依然存了指向類型的指針,所以拿這個interface變量去和nil常量進行比較的話就會返回false。

如何解決這個問題

想要避開這個Go語言的坑,我們要做的就是避免將一個有可能為nil的具體類型的值賦值給interface變量。以上述的OpenRedis為例,一種方法是先對OpenRedis返回的結果進行非-nil檢查,然后再賦值給interface變量,如下所示。

var storageEngine StorageEngine // 這是一個全局變量redis := OpenRedis(cfg.Url, dbindex)if redis != nil {  // 連接成功  storageEngine = redis // 確定redis不是nil之后再賦值給interface變量} else {  // 連接失敗  ...}

另外一種方法是讓OpenRedis函數直接返回EntityStorage接口類型的值,這樣就可以把OpenRedis的返回值直接正確賦值給EntityStorage接口變量。

// OpenRedis opens redis as entity storagefunc OpenRedis(url string, dbindex int) EntityStorage { c, err := redis.DialURL(url) if err != nil { return nil } if dbindex >= 0 { if _, err := c.Do("SELECT", dbindex); err != nil {  return nil } } es := &redisEntityStorage{ c: c, } return es}

至于那種方法更好,就見仁見智了。希望大家在實際項目中不要踩坑,即使踩了也能快速跳出來!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 亚洲成av人片在线观看 | 久久婷婷香蕉 | 欧美精品综合在线 | 久久精品国产99 | 精品一区二区三区视频 | 国产伦精品一区二区 | 91亚洲国产精品 | 不卡视频一区二区三区 | 欧美激情欧美激情在线五月 | 久久国产麻豆 | 国产成人精品综合 | 欧美日本国产欧美日本韩国99 | 超碰一区 | 五月激情六月天 | 中文字幕日韩欧美 | 91精品视频一区 | 精品视频免费观看 | 亚洲一级黄色片子 | 国产suv精品一区二区六 | 天堂va| 国产成人精品一区二区在线 | 99成人| 欧美精品1区2区3区 亚洲区在线 | 久久精品综合 | 日本黄色电影网 | 一本色道久久99精品综合 | 久久这里只有精品23 | 日韩久久一区二区三区 | 日韩aaa| 色婷婷一区二区三区四区 | 日日躁夜夜操 | 欧美影院一区二区三区 | 欧美 日韩 国产 成人 在线 | 手机看片169 | 青娱乐国产 | 精品国产髙清在线看国产毛片 | 久久性| 日韩性视频 | 成人一区二区三区久久精品嫩草 | 在线日韩一区 | 国产精品一区在线观看 |