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

Go Comparable Type原理深入解析

 更新時(shí)間:2023年01月06日 11:34:16   作者:sorcererxw  
這篇文章主要為大家介紹了Go Comparable Type原理深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

介紹

在 Go reflect 包里面對(duì) Type 有一個(gè) Comparable 的定義:

package reflect
type Type interface {
	// Comparable reports whether values of this type are comparable.
	Comparable() bool
}

正如字面意思,Comparable 表示一個(gè)類型是否可以直接使用運(yùn)算符比較。Go spec 羅列了所有可比較的類型,其中將可比較性劃分為兩個(gè)維度(如果不符合要求,會(huì)直接在編譯期報(bào)錯(cuò)):

  • Comparable:可以使用 == 和 != 比較,非黑即白
  • Ordered:可以使用 > >= < <= 做大小比較,有明確的大小概念

我簡(jiǎn)單整理了一下所有 Go 內(nèi)置類型的約定:

TypeComparableOrderedDescription
Boolean??
Integer??
Float??
Complex??分別比較實(shí)數(shù)和虛數(shù),同時(shí)相等則兩個(gè)復(fù)數(shù)相等。 如果需要比較大小,需要開發(fā)者分別比較實(shí)數(shù)和虛數(shù)。
String??基于字節(jié)逐個(gè)比較。
Pointer??如果兩個(gè)指針指向同一個(gè)對(duì)象或者都為 nil,則兩者相等。
Channel??類似 Pointer,兩個(gè) Channel 變量只有都為 nil,或者指向同一個(gè) Channel 的時(shí)候才相等。
Interface??兩個(gè) interface 的 Type 和 Value 值同時(shí)相等時(shí),兩者才相等。
Struct???僅當(dāng) Struct 內(nèi)所有成員都是 Comparable,這個(gè) Struct 才是 Comparable 的。 如果兩個(gè) struct 類型相同,且所有非空成員變量都相等,則兩者相等。
Array???僅當(dāng)成員為 Comparable,Array 才是 Comparable 的。 如果兩個(gè) Array 中的每一個(gè)元素一一相等時(shí),則兩個(gè) Array 相等。
Map??
Slice??
Func??

從上面可以看到,Go 當(dāng)中絕大多數(shù)類型都是可以使用運(yùn)算符相互比較的,唯獨(dú)不包含 Slice,Map 和 Func,也有容器類型 Struct、Array 本身的 Comparable 取決于成員的類型。

內(nèi)部實(shí)現(xiàn)

知道了語(yǔ)法約定,我們可以看一下 reflect 具體是怎么判斷一個(gè)變量的 Comparable 屬性:

type rtype struct {
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
}
func (t *rtype) Comparable() bool {
	return t.equal != nil
}

很簡(jiǎn)單,其實(shí)就是為每一個(gè)類型配備了一個(gè) equal 比較函數(shù),如果有這個(gè)函數(shù)則是 comparable。

上面的 rtype 結(jié)構(gòu)就包含在所有類型的內(nèi)存頭部:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}

所以如果希望知道某一個(gè)類型的 equal 需要翻閱對(duì)應(yīng)類型源碼。通過編譯 SSA 可以找到對(duì)應(yīng)類型的比較函數(shù)。

比如在 go/src/runtime/alg.go 下可以看到 interface 的 equal 函數(shù)的具體實(shí)現(xiàn):

func efaceeq(t *_type, x, y unsafe.Pointer) bool {
	if t == nil {
		return true
	}
	eq := t.equal
	if eq == nil {
		panic(errorString("comparing uncomparable type " + t.string()))
	}
	if isDirectIface(t) { // t.kind == kindDirectIface
		// Direct interface types are ptr, chan, map, func, and single-element structs/arrays thereof.
		// Maps and funcs are not comparable, so they can't reach here.
		// Ptrs, chans, and single-element items can be compared directly using ==.
		return x == y
	}
	return eq(x, y)
}

現(xiàn)實(shí)中的陷阱與應(yīng)用

在知道上面的設(shè)定之后,可以理解很多我們?cè)陂_發(fā)當(dāng)中碰到的錯(cuò)誤。

errors.Is

我們常常在模塊內(nèi)定義錯(cuò)誤時(shí),會(huì)定義出如下類型:

type CustomError struct {
	Metadata map[string]string
	Message string
}
func (c CustomError) Error() string {
		return c.Message
}
var (
	ErrorA = CustomError{Message:"A", Matadata: map[string]string{"Reason":""}}
	ErrorB = CustomError{Message:"B"}
)
func DoSomething() error {
	return ErrorA
}

而我們?cè)谕獠拷邮盏藉e(cuò)誤之后常常會(huì)使用 errors.Is 來判斷錯(cuò)誤類型:

err:=DoSomething()
if errors.Is(err, ErrorA) {
	// handle err
}

但是會(huì)發(fā)現(xiàn)上面這個(gè)判斷無論如何都是 false。研究一下 errors.Is 的源碼:

func Is(err, target error) bool {
	if target == nil {
		return err == target
	}
	isComparable := reflect.TypeOf(target).Comparable()
	for {
		if isComparable && err == target {
			return true
		}
		if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
			return true
		}
		if err = errors.Unwrap(err); err == nil {
			return false
		}
	}
}

可以看到這是一個(gè)在 error tree 上遞歸的流程,真值的終結(jié)條件是 err==target ,但是前提是 target 本身得是 comparable 的。

A comparison of two interface values with identical dynamic types causes a run-time panic if values of that type are not comparable.

如上描述,如果不加上這一段約束,會(huì)引發(fā) panic。

所以如果我們把一個(gè) map 放入了 error struct,就導(dǎo)致這個(gè) error 變?yōu)?incomparable,永遠(yuǎn)無法成功比較。

解決方案也很簡(jiǎn)單,就是將 Error 定義指針類型:

var (
	ErrorA = &amp;CustomError{Message:"A", Matadata: map[string]string{"Reason":""}}
	ErrorB = &amp;CustomError{Message:"B"}
)

指針類型比較只需要是否檢查是否指向同一個(gè)對(duì)象,這樣就能順利比較了。

(*Type)(nil) ≠ nil

這是 Go FAQ 的其中一條

func returnsError() error {
	var p *MyError = nil
	if bad() {
		p = ErrBad
	}
	return p // Will always return a non-nil error.
}

上面返回的 p 永遠(yuǎn)不會(huì)與 nil 相等。

這是為什么呢,因?yàn)?error 是一個(gè) interface,從上面可以知道,interface 之間比較需要保證兩者的 Type 和 Value 兩兩相等:

  • 語(yǔ)言內(nèi)的 nil 可以理解為一個(gè) Type 和 Value 均為空的 interface
  • 代碼里面返回的 p 雖然 Value 為空,但是 Type 是 *MyError

所以 p!=nil 。

正確的代碼應(yīng)該是這樣的:

func returnsError() error {
	if bad() {
		return ErrBad
	}
	return nil
}

這個(gè)問題不僅僅是拋出錯(cuò)誤的時(shí)候會(huì)出現(xiàn),任何返回 interface 的場(chǎng)景都需要注意。

Context Value Key

Go 的 Context 可以存取一些全局變量,其存儲(chǔ)方式是一個(gè)樹狀結(jié)構(gòu),每一次取值的時(shí)候就會(huì)從當(dāng)前節(jié)點(diǎn)一路遍歷到根節(jié)點(diǎn),查找是否有對(duì)應(yīng)的 Key:

func (c *valueCtx) Value(key interface{}) interface{} {
	if c.key == key {
		return c.val
	}
	return c.Context.Value(key)
}

那么就可能會(huì)出現(xiàn)因?yàn)樽庸?jié)點(diǎn)的 Key 與其中一個(gè)父節(jié)點(diǎn)的 Key 相同,導(dǎo)致 Value 被錯(cuò)誤地覆蓋。比如:

ctx = Context.Background()
ctx = context.WithValue(ctx, "key", "123")
ctx = context.WithValue(ctx, "key", "456")
ctx.Value("key") // 456

因?yàn)?Context 是全鏈路透?jìng)鞯?,誰(shuí)都沒法保證一個(gè) Key 是否會(huì)被其中某一層覆蓋。這個(gè)問題本質(zhì)上是:當(dāng)Key 的類型為 Integer/Float/String/Complex 時(shí),"偽造"一個(gè)值相同的 Key 太容易了。那么我們可以運(yùn)用 Go Comparable 的特性,選擇無法被"偽造"的類型作為 Key。推薦兩種比較優(yōu)雅的方式:

指針類型

var key = byte(0)
ctx = context.WithValue(ctx, &key, "123")
ctx.Value(&key)

這樣一來,除了包內(nèi)函數(shù),沒有其他代碼還能構(gòu)造出相同的指針了。

Struct 類型

從上文可以知道,strcut 只要類型相同,內(nèi)部的值相等,就能直接使用 == 判斷相等,那么我們可以直接使用 struct 作為 Key。

type key struct {}
ctx = context.WithValue(ctx, key{}, "123")
ctx.Value(key{})

同樣的,我們把 struct 定義為私有類似,包外也無法構(gòu)造出相同的 key。

我們知道空 struct 是不占用內(nèi)存的,這么做相比指針類型的 Key,可以減少內(nèi)存開銷。

以上就是Go Comparable Type原理深入解析的詳細(xì)內(nèi)容,更多關(guān)于Go Comparable Type原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go設(shè)計(jì)模式之備忘錄模式圖文詳解

    Go設(shè)計(jì)模式之備忘錄模式圖文詳解

    備忘錄模式是一種行為設(shè)計(jì)模式, 允許在不暴露對(duì)象實(shí)現(xiàn)細(xì)節(jié)的情況下保存和恢復(fù)對(duì)象之前的狀態(tài),本文主要通過一些圖片來給大家介紹一下Go的備忘錄模式,需要的朋友可以參考下
    2023-08-08
  • Go?viper讀取配置文件的示例詳解

    Go?viper讀取配置文件的示例詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言如何利用viper實(shí)現(xiàn)讀取配置文件,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下
    2023-08-08
  • golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn)

    golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn)

    本文主要介紹了golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn),在Iris里面,提供了一種方式可以讓我們同時(shí)運(yùn)行多個(gè)應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Go語(yǔ)言中結(jié)構(gòu)體方法副本傳參與指針傳參的區(qū)別介紹

    Go語(yǔ)言中結(jié)構(gòu)體方法副本傳參與指針傳參的區(qū)別介紹

    這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中結(jié)構(gòu)體方法副本傳參與指針傳參的區(qū)別的相關(guān)資料,文中先對(duì)GO語(yǔ)言結(jié)構(gòu)體方法跟結(jié)構(gòu)體指針方法的區(qū)別進(jìn)行了一些簡(jiǎn)單的介紹,來幫助大家理解學(xué)習(xí),需要的朋友可以參考下。
    2017-12-12
  • Go語(yǔ)言讀取文件的四種方式

    Go語(yǔ)言讀取文件的四種方式

    本文主要介紹了Go語(yǔ)言讀取文件的四種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • golang xorm日志寫入文件中的操作

    golang xorm日志寫入文件中的操作

    這篇文章主要介紹了golang xorm日志寫入文件中的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang內(nèi)存對(duì)齊詳解

    golang內(nèi)存對(duì)齊詳解

    在golang中,每一種數(shù)據(jù)類型都有其對(duì)應(yīng)的數(shù)據(jù)類型大小,也就是占用了多少內(nèi)存空間,我們可以通過unsafe.Sizeof函數(shù),來確定一個(gè)變量占用的內(nèi)存字節(jié)數(shù),本文將詳細(xì)給大家介紹golang內(nèi)存對(duì)齊,需要的朋友可以參考下
    2023-10-10
  • go語(yǔ)言實(shí)現(xiàn)http服務(wù)端與客戶端的例子

    go語(yǔ)言實(shí)現(xiàn)http服務(wù)端與客戶端的例子

    今天小編就為大家分享一篇go語(yǔ)言實(shí)現(xiàn)http服務(wù)端與客戶端的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • Go錯(cuò)誤和異常CGO?fallthrough處理教程詳解

    Go錯(cuò)誤和異常CGO?fallthrough處理教程詳解

    這篇文章主要為大家介紹了Go錯(cuò)誤和異常CGO?fallthrough使用教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Golang標(biāo)準(zhǔn)庫(kù)之errors包應(yīng)用方式

    Golang標(biāo)準(zhǔn)庫(kù)之errors包應(yīng)用方式

    Go語(yǔ)言的errors包提供了基礎(chǔ)的錯(cuò)誤處理能力,允許通過errors.New創(chuàng)建自定義error對(duì)象,error在Go中是一個(gè)接口,通過實(shí)現(xiàn)Error方法來定義錯(cuò)誤文本,對(duì)錯(cuò)誤的比較通?;趯?duì)象地址,而非文本內(nèi)容,因此即使兩個(gè)錯(cuò)誤文本相同
    2024-10-10

最新評(píng)論

迭部县| 分宜县| 缙云县| 静乐县| 明溪县| 遂昌县| 西乌珠穆沁旗| 肃北| 保亭| 视频| 北宁市| 个旧市| 平阳县| 灌云县| 静宁县| 道孚县| 苏尼特右旗| 车险| 合水县| 炉霍县| 虹口区| 阿图什市| 民勤县| 高要市| 于都县| 昌都县| 长宁区| 克山县| 巢湖市| 曲水县| 铁力市| 汽车| 静乐县| 河北区| 秀山| 涞水县| 揭阳市| 牟定县| 治县。| 沙坪坝区| 宝丰县|