<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          在 Go 中如何讓結(jié)構(gòu)體不可比較?

          共 5533字,需瀏覽 12分鐘

           ·

          2024-06-19 08:52

          最近我在使用 Go 官方出品的結(jié)構(gòu)化日志包 slog 時,看到 slog.Value 源碼中有一個比較好玩的小 Tips,可以限制兩個結(jié)構(gòu)體之間的相等性比較,本文就來跟大家分享下。

          在 Go 中結(jié)構(gòu)體可以比較嗎?

          在 Go 中結(jié)構(gòu)體可以比較嗎?這其實是我曾經(jīng)面試過的一個問題,我們來做一個實驗:

          定義如下結(jié)構(gòu)體:

          type Normal struct {
           a string
           B int
          }

          使用這個結(jié)構(gòu)體分別聲明 3 個變量 n1、n2、n3,然后進(jìn)行比較:

          n1 := Normal{
           a: "a",
           B: 10,
          }
          n2 := Normal{
           a: "a",
           B: 10,
          }
          n3 := Normal{
           a: "b",
           B: 20,
          }

          fmt.Println(n1 == n2)
          fmt.Println(n1 == n3)

          執(zhí)行示例代碼,輸出結(jié)果如下:

          $ go run main.go
          true
          false

          可見 Normal 結(jié)構(gòu)體是可以比較的。

          如何讓結(jié)構(gòu)體不可比較?

          那么所有結(jié)構(gòu)體都可以比較嗎?顯然不是,如果都可以比較,那么 reflect.DeepEqual() 就沒有存在的必要了。

          定義如下結(jié)構(gòu)體:

          type NoCompare struct {
           a string
           B map[string]int
          }

          使用這個結(jié)構(gòu)體分別聲明 2 個變量 n1n2,然后進(jìn)行比較:

          n1 := NoCompare{
           a: "a",
           B: map[string]int{
            "a"10,
           },
          }
          n2 := NoCompare{
           a: "a",
           B: map[string]int{
            "a"10,
           },
          }

          fmt.Println(n1 == n2)

          執(zhí)行示例代碼,輸出結(jié)果如下:

          $ go run main.go
          ./main.go:59:15: invalid operation: n1 == n2 (struct containing map[string]int cannot be compared)

          這里程序直接報錯了,并提示結(jié)構(gòu)體包含了 map[string]int 類型字段,不可比較。

          所以小結(jié)一下:

          結(jié)構(gòu)體是否可以比較,不取決于字段是否可導(dǎo)出,而是取決于其是否包含不可比較字段。

          如果全部字段都是可比較的,那么這個結(jié)構(gòu)體就是可比較的。

          如果其中有一個字段不可比較,那么這個結(jié)構(gòu)體就是不可比較的。

          不過雖然我們不可以使用 ==n1n2 進(jìn)行比較,但我們可以使用 reflect.DeepEqual() 對二者進(jìn)行比較:

          fmt.Println(reflect.DeepEqual(n1, n2))

          執(zhí)行示例代碼,輸出結(jié)果如下:

          $ go run main.go
          true

          更優(yōu)雅的做法

          最近我在使用 Go 官方出品的結(jié)構(gòu)化日志包 slog 時,看到 slog.Value 源碼:

          // A Value can represent any Go value, but unlike type any,
          // it can represent most small values without an allocation.
          // The zero Value corresponds to nil.
          type Value struct {
           _ [0]func() // disallow ==
           // num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
           // the string length for KindString, and nanoseconds since the epoch for KindTime.
           num uint64
           // If any is of type Kind, then the value is in num as described above.
           // If any is of type *time.Location, then the Kind is Time and time.Time value
           // can be constructed from the Unix nanos in num and the location (monotonic time
           // is not preserved).
           // If any is of type stringptr, then the Kind is String and the string value
           // consists of the length in num and the pointer in any.
           // Otherwise, the Kind is Any and any is the value.
           // (This implies that Attrs cannot store values of type Kind, *time.Location
           // or stringptr.)
           any any
          }

          可以發(fā)現(xiàn),這里有一個匿名字段 _ [0]func(),并且注釋寫著 // disallow ==。

          _ [0]func() 的目的顯然是為了禁止比較。

          我們來實驗一下,_ [0]func() 是否能夠?qū)崿F(xiàn)禁止結(jié)構(gòu)體相等性比較:

          v1 := Value{
           num: 1,
           any: 2,
          }
          v2 := Value{
           num: 1,
           any: 2,
          }

          fmt.Println(v1 == v2)

          執(zhí)行示例代碼,輸出結(jié)果如下:

          $ go run main.go
          ./main.go:109:15: invalid operation: v1 == v2 (struct containing [0]func() cannot be compared)

          可以發(fā)現(xiàn),的確有效。因為 func() 是一個函數(shù),而函數(shù)在 Go 中是不可比較的。

          既然使用 map[string]int_ [0]func() 都能實現(xiàn)禁止結(jié)構(gòu)體相等性比較,那么我為什么說 _ [0]func() 是更優(yōu)雅的做法呢?

          _ [0]func() 有著比其他實現(xiàn)方式更優(yōu)的特點:

          它不占內(nèi)存空間!

          使用匿名字段 _ 語義也更強。

          而且,我們直接去 Go 源碼里搜索,能夠發(fā)現(xiàn)其實 Go 本身也在多處使用了這種用法:

          _ [0]func()

          所以推薦使用 _ [0]func() 來實現(xiàn)禁用結(jié)構(gòu)體相等性比較。

          不過值得注意的是:當(dāng)使用 _ [0]func() 時,不要把它放在結(jié)構(gòu)體最后一個字段,推薦放在第一個字段。這與結(jié)構(gòu)體內(nèi)存對齊有關(guān),我在《Go 中空結(jié)構(gòu)體慣用法,我?guī)湍憧偨Y(jié)全了!》 一文中也有提及。

          NOTE: 對于 _ [0]func() 不占用內(nèi)存空間的驗證,就交給你自己去實驗了。提示:可以使用 fmt.Println(unsafe.Sizeof(v1), unsafe.Sizeof(v2)) 分別打印結(jié)構(gòu)體 Value 的兩個實例 v1v2 的內(nèi)存大小。你可以刪掉 _ [0]func() 字段再試一試。

          總結(jié)

          好了,在 Go 中如何讓結(jié)構(gòu)體不可比較這個小 Tips 就分享給大家了,還是比較有意思的。

          我在看到 slog.Value 源碼使用 _ [0]func() 來禁用結(jié)構(gòu)體相等性比較時,又搜索了 Go 的源碼中多處在使用,我想這應(yīng)該是社區(qū)推薦的做法了。然后就嘗試去網(wǎng)上搜索了下,還真被我搜索到了一個叫 Phuong Le 的人在 X 上發(fā)布了 Golang Tip #50: Make Structs Non-comparable. 專門來介紹這個 Tip,并且我在中文社區(qū)也找到了鳥窩老師在《Go語言編程技巧》中的譯文 Tip #50 使結(jié)構(gòu)體不可比較。

          這也印證了我的猜測,_ [0]func() 在 Go 社區(qū)中是推薦用法。

          本文示例源碼我都放在了 GitHub 中,歡迎點擊查看。

          希望此文能對你有所啟發(fā)。

          延伸閱讀

          • slog 源碼: https://github.com/golang/go/blob/master/src/log/slog/value.go#L21
          • Golang Tip #50: Make Structs Non-comparable.: https://x.com/func25/status/1768621711929311620
          • Go語言編程技巧: https://colobu.com/gotips/050.html
          • 本文 GitHub 示例代碼:https://github.com/jianghushinian/blog-go-example/tree/main/struct/non-comparable



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 272
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本成人大香蕉视频在线观看 | 青娱乐成人在线网址 | 永久在线一区 | 亚洲无码操逼 | 91在线观看视频 |