Go1.17 新特性之切片變數(shù)組
閱讀本文大概需要 6 分鐘。
大家好,我是 polarisxu。
按計劃,Go 1.17 會在 2021 年 8 月份發(fā)布(目前已經(jīng)發(fā)布了 Beta1 版本)。目前,1.17 相關(guān)的功能已經(jīng)開發(fā)差不多了,上次介紹了測試順序隨機的問題,今天介紹 1.17 中的另一個新功能:切片顯式地轉(zhuǎn)換成數(shù)組指針。
溫馨提示,如果要試驗該功能,需要升級到 1.17 Beta1 版本。另外一個主意事項就是如果在有 go.mod 的目錄中試驗,確保其中的版本改為 1.17,否則會報錯:conversion of slices to array pointers only supported as of -lang=go1.17
01 數(shù)組轉(zhuǎn)切片
介紹新功能之前,我們先看看在 Go 中如何將數(shù)組轉(zhuǎn)為切片。(當然,數(shù)組指針也是 OK 的)
一般地,通過 slice 表達式(slice expressions)可以從一個數(shù)組得到一個切片。
a[low : high : max]
其中,max 可以省略。比如:
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]
s 就是一個切片。
02 切片轉(zhuǎn)數(shù)組指針
先了解下,為什么會有這樣的需求。
該需求來自這個 issue:https://github.com/golang/go/issues/395。rogpeppe 提到,很多時候,函數(shù)接收一個 slice 參數(shù),但如果使用數(shù)組指針,則允許編譯器在編譯時檢查常量索引。比如這樣的情況:
func foo(a []int) int {
return a[0] + a[1] + a[2] + a[3];
}
能夠編譯期進行索引檢查。比如這樣(當然,最后實現(xiàn)不是這樣的):
func foo(a []int) int {
b := a.[0:4];
return b[0] + b[1] + b[2] + b[3];
}
此外,有時候我們通過數(shù)組得到切片,但有時候我們直接創(chuàng)建切片,底層數(shù)組是匿名的。如果我們想要獲得底層數(shù)組怎么辦?將切片轉(zhuǎn)為數(shù)組指針可以實現(xiàn)這個需求。
看看具體的例子,以下來自 Go 語言規(guī)范(針對 Go1.17 這個語言特性新增):
s := make([]byte, 2, 4)
s0 := (*[0]byte)(s) // s0 != nil
s2 := (*[2]byte)(s) // &s2[0] == &s[0]
s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s)
var t []string
t0 := (*[0]string)(t) // t0 == nil
t1 := (*[1]string)(t) // panics: len([1]string) > len(s)
幾個注意的點:
當切片的長度小于數(shù)組長度(len)時會 panic。所以上面例子中,s4 和 t1 發(fā)生了 panic 將一個非空切片轉(zhuǎn)為 0 長度的數(shù)組,得到的指針不是 nil(如 s0);但將一個空切片轉(zhuǎn)為 0 長度的數(shù)組,得到的指針是 nil(如 t0); 多次轉(zhuǎn)換,并不會創(chuàng)建多個數(shù)組(因為得到的是底層數(shù)組),這從 &s2[0] == &s[0]可以看出;
所以,總結(jié)一下就是,將切片轉(zhuǎn)換為數(shù)組指針,產(chǎn)生指向切片的底層數(shù)組的指針。如果切片的長度小于數(shù)組的長度,則會發(fā)生運行時 panic。
不過針對 panic,目前沒法做斷言檢查。只能通過 if 判斷了。
03 reflect 注意事項
針對語言這個改動,reflect 包中的 Type 接口有一個方法:ConvertibleTo。之前的說明是這樣的:
// ConvertibleTo reports whether a value of the type is convertible to type u.
ConvertibleTo(u Type) bool
1.17 是這樣的:
// ConvertibleTo reports whether a value of the type is convertible to type u.
// Even if ConvertibleTo returns true, the conversion may still panic.
// For example, a slice of type []T is convertible to *[N]T,
// but the conversion will panic if its length is less than N.
ConvertibleTo(u Type) bool
因為切片轉(zhuǎn)為數(shù)組指針可能會 panic,所以才加了這么一句文檔說明。
因此,如果通過反射轉(zhuǎn)換做類型轉(zhuǎn)換,雖然通過 ConvertibleTo 判斷是可轉(zhuǎn)換的,但調(diào)用 Convert 方法依然可能 panic。這點需要特別注意下。
04 小結(jié)
這個語言改變,大部分時候可能用不到。但有些場景可以做到不需要內(nèi)存拷貝(copy),比如標準庫中有一個例子:
// https://docs.studygolang.com/src/crypto/sha256/sha256.go?s=5787:5834#L252
func Sum224(data []byte) (sum224 [Size224]byte) {
var d digest
d.is224 = true
d.Reset()
d.Write(data)
sum := d.checkSum()
copy(sum224[:], sum[:Size224])
return
}
官方計劃修改為:
func Sum224(data []byte) [Size224]byte {
var d digest
d.is224 = true
d.Reset()
d.Write(data)
sum := d.checkSum()
ap := (*[Size224]byte)(sum[:Size224])
return *ap
}
注意其中的區(qū)別。
但這里 bradfitz 在修改時,發(fā)現(xiàn),為什么一定要轉(zhuǎn)為數(shù)組指針,能否直接轉(zhuǎn)為數(shù)組,畢竟,在 Go 中使用數(shù)組的話,不太常用數(shù)組指針。于是 bradfitz 給出了另一個提案:https://github.com/golang/go/issues/46505,即 allow conversion from slice to array。目前該提案是否接受,還沒有結(jié)論。
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標準庫》等。
堅持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio
