最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go疑難雜癥講解之為什么nil不等于nil

 更新時(shí)間:2022年10月27日 14:02:28   作者:弗蘭克的貓  
在日常開發(fā)中,可能一不小心就會(huì)掉進(jìn)?Go?語言的某些陷阱里,而本文要介紹的?nil?≠?nil?問題,感興趣的小伙伴可以跟隨小編一起了解一下

現(xiàn)象

在日常開發(fā)中,可能一不小心就會(huì)掉進(jìn) Go 語言的某些陷阱里,而本文要介紹的 nil ≠ nil 問題,便是其中一個(gè),初看起來會(huì)讓人覺得很詭異,摸不著頭腦。

先來看個(gè)例子:

type CustomizedError struct {
	ErrorCode int
	Msg       string
}

func (e *CustomizedError) Error() string {
	return fmt.Sprintf("err code: %d, msg: %s", e.ErrorCode, e.Msg)
}
func main() {
	txn, err := startTx()
	if err != nil {
		log.Fatalf("err starting tx: %v", err)
	}

	if err = txn.doUpdate(); err != nil {
		log.Fatalf("err updating: %v", err)
	}

	if err = txn.commit(); err != nil {
		log.Fatalf("err committing: %v", err)
	}
	fmt.Println("success!")
}

type tx struct{}

func startTx() (*tx, error) {
	return &tx{}, nil
}

func (*tx) doUpdate() *CustomizedError {
	return nil
}

func (*tx) commit() error {
	return nil
}

這是一個(gè)簡(jiǎn)化過了的例子,在上述代碼中,我們創(chuàng)建了一個(gè)事務(wù),然后做了一些更新,在更新過程中如果發(fā)生了錯(cuò)誤,希望返回對(duì)應(yīng)的錯(cuò)誤碼和提示信息。

如果感興趣的話,可以在這個(gè)地址在線運(yùn)行這份代碼:

Go Playground - The Go Programming Language

看起來每個(gè)方法都會(huì)返回 nil,應(yīng)該能順利走到最后一行,輸出 success 才對(duì),但實(shí)際上,輸出的卻是:

err updating: <nil>

尋找原因

為什么明明返回的是 nil,卻被判定為 err ≠ nil 呢?難道這個(gè) nil 也有什么奇妙之處?

這就需要我們來更深入一點(diǎn)了解 error 本身了。在 Go 語言中, error 是一個(gè) interface ,內(nèi)部含有一個(gè) Error() 函數(shù),返回一個(gè)字符串,接口的描述如下:

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

而對(duì)于一個(gè)變量來說,它有兩個(gè)要素,一個(gè)是 type T,一個(gè)是 value V,如下圖所示:

來看一個(gè)簡(jiǎn)單的例子:

var it interface{}
fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // <nil> <invalid reflect.Value>
it = 1
fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // int 1
it = "hello"
fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // string hello
var s *string
it = s
fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string <nil>
ss := "hello"
it = &ss
fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string 0xc000096560

在給一個(gè) interface 變量賦值前,TV 都是 nil,但給它賦值后,不僅會(huì)改變它的值,還會(huì)改變它的類型。

當(dāng)把一個(gè)值為 nil 的字符串指針賦值給它后,雖然它的值是 V=nil,但它的類型 T 卻變成了 *string

此時(shí)如果拿它來跟 nil 比較,結(jié)果就會(huì)是不相等,因?yàn)?strong>只有當(dāng)這個(gè) interface 變量的類型和值都未被設(shè)置時(shí),它才真正等于 nil。

再來看看之前的例子中,err 變量的 TV 是如何變化的:

func main() {
	txn, err := startTx()
	fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))
	if err != nil {
		log.Fatalf("err starting tx: %v", err)
	}

	if err = txn.doUpdate(); err != nil {
		fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))
		log.Fatalf("err updating: %v", err)
	}

	if err = txn.commit(); err != nil {
		log.Fatalf("err committing: %v", err)
	}
	fmt.Println("success!")
}

輸出如下:

<nil> <invalid reflect.Value>
*err.CustomizedError <nil>

在一開始,我們給 err 初始化賦值時(shí),startTx 函數(shù)返回的是一個(gè) error 接口類型的 nil。此時(shí)查看其類型 T 和值 V 時(shí),都會(huì)是 nil。

txn, err := startTx()
fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // <nil> <invalid reflect.Value>

func startTx() (*tx, error) {
	return &tx{}, nil
}

而在調(diào)用 doUpdate 時(shí),會(huì)將一個(gè) *CustomizedError 類型的 nil 值賦值給了它,它的類型 T 便成了 *CustomizedError ,V 是 nil

err = txn.doUpdate()
fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // *err.CustomizedError <nil>

所以在做 err ≠ nil 的比較時(shí),err 的類型 T 已經(jīng)不是 nil,前面已經(jīng)說過,只有當(dāng)一個(gè)接口變量的 TV 同時(shí)為 nil 時(shí),這個(gè)變量才會(huì)被判定為 nil,所以該不等式會(huì)判定為 true。

要修復(fù)這個(gè)問題,其實(shí)最簡(jiǎn)單的方法便是在調(diào)用 doUpdate 方法時(shí)給 err 進(jìn)行重新聲明:

if err := txn.doUpdate(); err != nil {
		log.Fatalf("err updating: %v", err)
}

此時(shí),err 其實(shí)成了一個(gè)新的結(jié)構(gòu)體指針變量,而不再是一個(gè)interface 類型變量,類型為 *CustomizedError ,且值為 nil,所以做 err ≠ nil 的比較時(shí)結(jié)果就是將是 false

問題到這里似乎就告一段落了,但,再仔細(xì)想想,就會(huì)發(fā)現(xiàn)這其中似乎還是漏掉了一環(huán)。

如果給一個(gè) interface 類型的變量賦值時(shí),會(huì)同時(shí)改變它的類型 T 和值 V,那跟 nil 比較時(shí)為什么不是跟它的新類型對(duì)應(yīng)的 nil 比較呢?

事實(shí)上,interface 變量跟普通變量確實(shí)有一定區(qū)別,一個(gè)非空接口 interface (即接口中存在函數(shù)方法)初始化的底層數(shù)據(jù)結(jié)構(gòu)是 iface,一個(gè)空接口變量對(duì)應(yīng)的底層結(jié)構(gòu)體為 eface

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type eface struct {
	_type *_type
	data  unsafe.Pointer
}

tab 中存放的是類型、方法等信息。data 指針指向的 iface 綁定對(duì)象的原始數(shù)據(jù)的副本。

再來看一下 itab 的結(jié)構(gòu):

// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.
type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte // 用于內(nèi)存對(duì)齊
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

itab 中一共包含 5 個(gè)字段,inner 字段存的是初始化 interface 時(shí)的靜態(tài)類型。_type 存的是 interface 對(duì)應(yīng)具體對(duì)象的類型,當(dāng) interface 變量被賦值后,這個(gè)字段便會(huì)變成被賦值的對(duì)象的類型。

itab 中的 _typeiface 中的 data 便分別對(duì)應(yīng) interface 變量的 TV,_type 是這個(gè)變量對(duì)應(yīng)的類型,data 是這個(gè)變量的值。在之前的賦值測(cè)試中,通過 reflect.TypeOfreflect.ValueOf 方法獲取到的信息也分別來自這兩個(gè)字段。

這里的 hash 字段和 _type 中存的 hash 字段是完全一致的,這么做的目的是為了類型斷言。

fun 是一個(gè)函數(shù)指針,它指向的是具體類型的函數(shù)方法,在這個(gè)指針對(duì)應(yīng)內(nèi)存地址的后面依次存儲(chǔ)了多個(gè)方法,利用指針偏移便可以找到它們。

再來看看 interfacetype 的結(jié)構(gòu):

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

這其中也有一個(gè) _type 字段,來表示 interface 變量的初始類型。

看到這里,之前的疑問便開始清晰起來,一個(gè) interface 變量實(shí)際上有兩個(gè)類型,一個(gè)是初始化時(shí)賦值時(shí)對(duì)應(yīng)的 interface 類型,一個(gè)是賦值具體對(duì)象時(shí),對(duì)象的實(shí)際類型。

了解了這些之后,我們?cè)賮砜匆幌轮暗睦樱?/p>

txn, err := startTx()

這里先對(duì) err 進(jìn)行初始化賦值,此時(shí),它的 itab.inter.typ 對(duì)應(yīng)的類型信息就是 error itab._type 仍為 nil

err = txn.doUpdate()

當(dāng)對(duì) err 進(jìn)行重新賦值時(shí),erritab._type 字段會(huì)被賦值成 *CustomizedError ,所以此時(shí),err 變量實(shí)際上是一個(gè) itab.inter.typerror ,但實(shí)際類型為 *CustomizedError ,值為 nil 的接口變量。

把一個(gè)具體類型變量與 nil 比較時(shí),只需要判斷其 value 是否為 nil 即可,而把一個(gè)接口類型的變量與 nil 進(jìn)行比較時(shí),還需要判斷其類型 itab._type 是否為nil。

如果想實(shí)際看看被賦值后 err 對(duì)應(yīng)的 iface 結(jié)構(gòu),可以把 iface 相關(guān)的結(jié)構(gòu)體都復(fù)制到同一個(gè)包下,然后通過 unsafe.Pointer 進(jìn)行類型強(qiáng)轉(zhuǎn),就可以通過打斷點(diǎn)的方式來查看了。

func TestErr(t *testing.T) {
	txn, err := startTx()
	fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))
	if err != nil {
		log.Fatalf("err starting tx: %v", err)
	}

	p := (*iface)(unsafe.Pointer(&err))
	fmt.Println(p.data)

	if err = txn.doUpdate(); err != nil {
		fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))
		p := (*iface)(unsafe.Pointer(&err))
		fmt.Println(p.data)
		log.Fatalf("err updating: %v", err)
	}

	if err = txn.commit(); err != nil {
		log.Fatalf("err committing: %v", err)
	}
	fmt.Println("success!")
}

補(bǔ)充說明一下,這里的inter.typ.kind 表示的是變量的基本類型,其值對(duì)應(yīng) runtime 包下的枚舉。

const (
	kindBool = 1 + iota
	kindInt
	kindInt8
	kindInt16
	kindInt32
	kindInt64
	kindUint
	kindUint8
	kindUint16
	kindUint32
	kindUint64
	kindUintptr
	kindFloat32
	kindFloat64
	kindComplex64
	kindComplex128
	kindArray
	kindChan
	kindFunc
	kindInterface
	kindMap
	kindPtr
	kindSlice
	kindString
	kindStruct
	kindUnsafePointer

	kindDirectIface = 1 << 5
	kindGCProg      = 1 << 6
	kindMask        = (1 << 5) - 1
)

比如上圖中所示的 kind = 20 對(duì)應(yīng)的類型就是 kindInterface。

總結(jié)

  • 接口類型變量跟普通變量是有差異的,非空接口類型變量對(duì)應(yīng)的底層結(jié)構(gòu)是 iface ,空接口類型類型變量對(duì)應(yīng)的底層結(jié)構(gòu)是 eface。
  • iface 中有兩個(gè)跟類型相關(guān)的字段,一個(gè)表示的是接口的類型 inter,一個(gè)表示的是變量實(shí)際類型 _type 。
  • 只有當(dāng)接口變量的 itab._type 與 data 都為 nil 時(shí),也就是實(shí)際類型和值都未被賦值前,才真正等于 nil 。

以上就是Go疑難雜癥講解之為什么nil不等于nil的詳細(xì)內(nèi)容,更多關(guān)于Go nil不等于nil的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go計(jì)算某段代碼運(yùn)行所耗時(shí)間簡(jiǎn)單實(shí)例

    Go計(jì)算某段代碼運(yùn)行所耗時(shí)間簡(jiǎn)單實(shí)例

    這篇文章主要給大家介紹了關(guān)于Go計(jì)算某段代碼運(yùn)行所耗時(shí)間的相關(guān)資料,主要介紹了Golang記錄計(jì)算函數(shù)執(zhí)行耗時(shí)、運(yùn)行時(shí)間的一個(gè)簡(jiǎn)單方法,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-11-11
  • 淺談go-restful框架的使用和實(shí)現(xiàn)

    淺談go-restful框架的使用和實(shí)現(xiàn)

    這篇文章主要介紹了淺談go-restful框架的使用和實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • 如何使用大學(xué)教育郵箱下載golang等軟件(推薦)

    如何使用大學(xué)教育郵箱下載golang等軟件(推薦)

    這篇文章主要介紹了如何使用大學(xué)教育郵箱下載goland等軟件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Go 實(shí)現(xiàn)百萬WebSocket連接的方法示例

    Go 實(shí)現(xiàn)百萬WebSocket連接的方法示例

    這篇文章主要介紹了Go 實(shí)現(xiàn)百萬WebSocket連接的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Golang之模糊測(cè)試工具的使用

    Golang之模糊測(cè)試工具的使用

    本文主要介紹了Golang之模糊測(cè)試工具的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Go 語言中程序編譯過程詳解

    Go 語言中程序編譯過程詳解

    本文旨在深入探討Go語言的編譯機(jī)制和最新的模塊管理系統(tǒng)——Go Modules,通過詳細(xì)的示例和步驟,我們將演示從簡(jiǎn)單的 “Hello World” 程序到使用第三方庫的更復(fù)雜項(xiàng)目的開發(fā)過程,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • Go Sentinel 動(dòng)態(tài)數(shù)據(jù)源配置指南(示例詳解)

    Go Sentinel 動(dòng)態(tài)數(shù)據(jù)源配置指南(示例詳解)

    本文介紹了如何使用Go語言配置Sentinel的動(dòng)態(tài)數(shù)據(jù)源,并通過本地文件和Nacos兩種方式實(shí)現(xiàn)動(dòng)態(tài)配置,通過這種方式,可以靈活地管理和更新限流規(guī)則,提升系統(tǒng)的穩(wěn)定性和響應(yīng)速度,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • Go 語言 IDE 中的 VSCode 配置使用教程

    Go 語言 IDE 中的 VSCode 配置使用教程

    Gogland 是 JetBrains 公司推出的Go語言集成開發(fā)環(huán)境。這篇文章主要介紹了Go 語言 IDE 中的 VSCode 配置使用教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • 詳解如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎

    詳解如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎

    規(guī)則引擎的功能可以簡(jiǎn)化為當(dāng)滿足一些條件時(shí)觸發(fā)一些操作,通常使用 DSL 自定義語法來表述,本文給大家介紹了如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-05-05
  • Go并發(fā)編程中sync/errGroup的使用

    Go并發(fā)編程中sync/errGroup的使用

    本文主要介紹了Go并發(fā)編程中sync/errGroup的使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12

最新評(píng)論

富川| 万源市| 安龙县| 武宣县| 山东省| 天柱县| 临清市| 鄂温| 武胜县| 郧西县| 习水县| 赤峰市| 雷波县| 桑日县| 白城市| 四川省| 罗源县| 六枝特区| 彭阳县| 海林市| 古田县| 稷山县| 河池市| 灵川县| 登封市| 比如县| 大荔县| 四会市| 昌图县| 上犹县| 恭城| 长沙市| 芒康县| 沂水县| 普宁市| 郸城县| 根河市| 集安市| 名山县| 华蓥市| 安新县|