別亂用了,用新的。Go SliceHeader 和 StringHeader 將會被廢棄!

張哥說技術發表於2022-12-21

大家好,我是煎魚。

Go 語言中有個很經典的 (Slice|String)Header,經常出現在大家視野中,為此我寫了《Go SliceHeader 和 StringHeader,你知道嗎?》給大家介紹,避免被面試官卷到。

以重點來講,SliceHeader 是 Slice(切片)的執行時表現;StringHeader 是 String(字串)的執行時表現。

背景

為什麼這兩個執行時結構體受到了那麼多的關注呢?是因為常被用於如下場景:

  • 將 []byte 轉換為 string。
  • 將 string 轉換為 []byte。
  • 抓取資料指標(data pointer)欄位用於 ffi 或其他用途。
  • 將一種型別的 slice 轉換為另一種型別的 slice。

常見案例,可見如下程式碼:

s := "腦子進煎魚了?重背面試題(doge"
h := (*reflect.StringHeader)(unsafe.Pointer(&s))

又或是自己構造一個:

unsafe.Pointer(&reflect.StringHeader{
  Data: uintptr(unsafe.Pointer(&s.Data[0])),
  Len:  int(s.Size),
 })

似乎看起來沒什麼問題,所以在業內開啟了一種新的姿勢。那就是藉助 (String|Slice) Header 來實現零複製的 string 到 bytes 的轉換,得到了廣大開發者的使用。畢竟誰都想效能高一點。

如下轉換程式碼:

func main() {
 s := "腦子進煎魚了"
 v := string2bytes1(s)
 fmt.Println(v)
}

func string2bytes1(s string) []byte {
 stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))

 var b []byte
 pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
 pbytes.Data = stringHeader.Data
 pbytes.Len = stringHeader.Len
 pbytes.Cap = stringHeader.Len

 return b
}

當然,還有更多基於 Header 自己寫的轉換,甚至寫錯寫到洩露沒法被 GC 的,又或是丟擲 throw 致命錯誤查了幾周的。

問題

今年 Go 團隊進行了討論,透過分析、搜尋發現 reflect.SliceHeaderreflect.StringHeader 在業內經常被濫用,且使用不方便,很容易出錯(要命的是很隱性的那種)。

GitHub 搜尋發現大量使用者案例:

別亂用了,用新的。Go SliceHeader 和 StringHeader 將會被廢棄!

這個坑也在於,SliceHeader 和 StringHeader 的 Data 欄位(後稱資料指標):

type StringHeader struct {
   Data uintptr
   Len  int
}

型別是 uintptr不是 unsafe.Pointer。設什麼都可以,靈活度過於高,非常容易搞出問題。

Go1.20 新特性

在 Go1.20 起,在 unsafe 標準庫新增了 3 個函式來替代前面這兩個型別的使用。希望能夠進一步標準化,並提供額外的型別安全。

別亂用了,用新的。Go SliceHeader 和 StringHeader 將會被廢棄!

如下函式簽名:

  • func String(ptr *byte, len IntegerType) string:根據資料指標和字元長度構造一個新的 string。
  • func StringData(str string) *byte:返回指向該 string 的位元組陣列的資料指標。
  • func SliceData(slice []ArbitraryType) *ArbitraryType:返回該 slice 的資料指標。

新版本的用法將會變成:

func StringToBytes(s string) []byte {
    return unsafe.Slice(unsafe.StringData(s), len(s))
}

func BytesToString(b []byte) string {
    return unsafe.String(&b[0], len(b))
}

以往常用的 reflect.SliceHeaderreflect.StringHeader 將會被標註為被廢棄

如下圖:

別亂用了,用新的。Go SliceHeader 和 StringHeader 將會被廢棄!

這兩個型別老版本中一直被標記為不穩定、不可靠。

以後記得換用法了。

總結

StringHeader 和 SliceHeader 在工作中的底層工具包在前兩年非常常見,如果是一個積累比較久的專案可能在內部都偷偷引用了,目的就是為了讓自己的效能跑分更優一些。

但是其較大的自由度,帶來了過大的型別不安全。因此我在工作中也遇到有人用到莫名其妙拋致命錯誤,最後只能用排除法去查是哪裡的鍋。

在新版本 Go1.20 中,Go 官方引入了這幾個新函式。能夠進一步宣告這是 unsafe 的,也能提供一點點的型別安全檢查。

當然,新版本後,愛問這幾個零複製的面試官,也得重新整理一下知識庫了。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2928845/,如需轉載,請註明出處,否則將追究法律責任。

相關文章