欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go Comparable Type原理深入解析

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

介紹

在 Go reflect 包里面對 Type 有一個 Comparable 的定義:

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

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

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

我簡單整理了一下所有 Go 內置類型的約定:

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

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

內部實現

知道了語法約定,我們可以看一下 reflect 具體是怎么判斷一個變量的 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
}

很簡單,其實就是為每一個類型配備了一個 equal 比較函數,如果有這個函數則是 comparable。

上面的 rtype 結構就包含在所有類型的內存頭部:

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

所以如果希望知道某一個類型的 equal 需要翻閱對應類型源碼。通過編譯 SSA 可以找到對應類型的比較函數。

比如在 go/src/runtime/alg.go 下可以看到 interface 的 equal 函數的具體實現:

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)
}

現實中的陷阱與應用

在知道上面的設定之后,可以理解很多我們在開發(fā)當中碰到的錯誤。

errors.Is

我們常常在模塊內定義錯誤時,會定義出如下類型:

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
}

而我們在外部接收到錯誤之后常常會使用 errors.Is 來判斷錯誤類型:

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

但是會發(fā)現上面這個判斷無論如何都是 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
		}
	}
}

可以看到這是一個在 error tree 上遞歸的流程,真值的終結條件是 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.

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

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

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

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

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

(*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 永遠不會與 nil 相等。

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

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

所以 p!=nil 。

正確的代碼應該是這樣的:

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

這個問題不僅僅是拋出錯誤的時候會出現,任何返回 interface 的場景都需要注意。

Context Value Key

Go 的 Context 可以存取一些全局變量,其存儲方式是一個樹狀結構,每一次取值的時候就會從當前節(jié)點一路遍歷到根節(jié)點,查找是否有對應的 Key:

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

那么就可能會出現因為子節(jié)點的 Key 與其中一個父節(jié)點的 Key 相同,導致 Value 被錯誤地覆蓋。比如:

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

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

指針類型

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

這樣一來,除了包內函數,沒有其他代碼還能構造出相同的指針了。

Struct 類型

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

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

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

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

以上就是Go Comparable Type原理深入解析的詳細內容,更多關于Go Comparable Type原理的資料請關注腳本之家其它相關文章!

相關文章

  • golang validator參數校驗的實現

    golang validator參數校驗的實現

    這篇文章主要介紹了golang validator參數校驗的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-10-10
  • go語言接口用法實例分析

    go語言接口用法實例分析

    這篇文章主要介紹了go語言接口用法,實例分析了Go語言接口的定義及使用技巧,需要的朋友可以參考下
    2015-03-03
  • logrus hook輸出日志到本地磁盤的操作

    logrus hook輸出日志到本地磁盤的操作

    這篇文章主要介紹了logrus hook輸出日志到本地磁盤的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • Golang的鎖機制使用及說明

    Golang的鎖機制使用及說明

    這篇文章主要介紹了Golang的鎖機制使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • go語言實現將重要數據寫入圖片中

    go語言實現將重要數據寫入圖片中

    本文給大家分享的是go語言實現將數據的二進制形式寫入圖像紅色通道數據二進制的低位,從而實現將重要數據隱藏,有需要的小伙伴參考下吧。
    2015-03-03
  • Go?中的空白標識符下劃線

    Go?中的空白標識符下劃線

    這篇文章主要介紹了Go?中的空白標識符下劃線,空白標識符是未使用的值的占位符,由下劃線(_)表示,下文對其相關介紹需要的小伙伴可以參考一下
    2022-03-03
  • go語言題解LeetCode1160拼寫單詞示例詳解

    go語言題解LeetCode1160拼寫單詞示例詳解

    這篇文章主要為大家介紹了go語言題解LeetCode1160拼寫單詞示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Go語言中的數據競爭模式詳解

    Go語言中的數據競爭模式詳解

    這篇文章主要介紹了Go語言中的數據競爭模式詳解,主要基于在Uber的Go monorepo中發(fā)現的各種數據競爭模式,分析了其背后的原因與分類,需要的朋友可以參考一下
    2022-07-07
  • Golang指針隱式間接引用詳解

    Golang指針隱式間接引用詳解

    在 Go中,指針隱式解引用是指通過指針直接訪問指針所指向的值,而不需要顯式地使用 * 運算符來解引用指針,這篇文章主要介紹了Golang指針隱式間接引用,需要的朋友可以參考下
    2023-05-05
  • Go語言的隊列和堆棧實現方法

    Go語言的隊列和堆棧實現方法

    這篇文章主要介紹了Go語言的隊列和堆棧實現方法,涉及container/list包的使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02

最新評論