1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        在 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,然后進行比較:

        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,然后進行比較:

        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)體是否可以比較,不取決于字段是否可導出,而是取決于其是否包含不可比較字段。

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

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

        不過雖然我們不可以使用 ==n1、n2 進行比較,但我們可以使用 reflect.DeepEqual() 對二者進行比較:

        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)體相等性比較。

        不過值得注意的是:當使用 _ [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 的兩個實例 v1、v2 的內(nèi)存大小。你可以刪掉 _ [0]func() 字段再試一試。

        總結(jié)

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

        我在看到 slog.Value 源碼使用 _ [0]func() 來禁用結(jié)構(gòu)體相等性比較時,又搜索了 Go 的源碼中多處在使用,我想這應該是社區(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



        推薦閱讀


        福利

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

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

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            色偷偷资源网 | 男人裸体性做爰全过 | 操逼123网| 亚洲精品无码久久毛片18特黄 | 美女扒开腿秘 男人爽桶网站 | 日产国产欧美一区二区三区app | 2022黄片 | 亚洲视频在线观看 | 久久爽av亚洲精品天堂 | 久久无精品国产99久久果冻传媒 |