Go1.17 新特性之切片變數(shù)組
閱讀本文大概需要 6 分鐘。
大家好,我是 polarisxu。
按計(jì)劃,Go 1.17 會(huì)在 2021 年 8 月份發(fā)布(目前已經(jīng)發(fā)布了 Beta1 版本)。目前,1.17 相關(guān)的功能已經(jīng)開(kāi)發(fā)差不多了,上次介紹了測(cè)試順序隨機(jī)的問(wèn)題,今天介紹 1.17 中的另一個(gè)新功能:切片顯式地轉(zhuǎn)換成數(shù)組指針。
溫馨提示,如果要試驗(yàn)該功能,需要升級(jí)到 1.17 Beta1 版本。另外一個(gè)主意事項(xiàng)就是如果在有 go.mod 的目錄中試驗(yàn),確保其中的版本改為 1.17,否則會(huì)報(bào)錯(cuò):conversion of slices to array pointers only supported as of -lang=go1.17
01 數(shù)組轉(zhuǎn)切片
介紹新功能之前,我們先看看在 Go 中如何將數(shù)組轉(zhuǎn)為切片。(當(dāng)然,數(shù)組指針也是 OK 的)
一般地,通過(guò) slice 表達(dá)式(slice expressions)可以從一個(gè)數(shù)組得到一個(gè)切片。
a[low : high : max]
其中,max 可以省略。比如:
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]
s 就是一個(gè)切片。
02 切片轉(zhuǎn)數(shù)組指針
先了解下,為什么會(huì)有這樣的需求。
該需求來(lái)自這個(gè) issue:https://github.com/golang/go/issues/395。rogpeppe 提到,很多時(shí)候,函數(shù)接收一個(gè) slice 參數(shù),但如果使用數(shù)組指針,則允許編譯器在編譯時(shí)檢查常量索引。比如這樣的情況:
func foo(a []int) int {
return a[0] + a[1] + a[2] + a[3];
}
能夠編譯期進(jìn)行索引檢查。比如這樣(當(dāng)然,最后實(shí)現(xiàn)不是這樣的):
func foo(a []int) int {
b := a.[0:4];
return b[0] + b[1] + b[2] + b[3];
}
此外,有時(shí)候我們通過(guò)數(shù)組得到切片,但有時(shí)候我們直接創(chuàng)建切片,底層數(shù)組是匿名的。如果我們想要獲得底層數(shù)組怎么辦?將切片轉(zhuǎn)為數(shù)組指針可以實(shí)現(xiàn)這個(gè)需求。
看看具體的例子,以下來(lái)自 Go 語(yǔ)言規(guī)范(針對(duì) Go1.17 這個(gè)語(yǔ)言特性新增):
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)
幾個(gè)注意的點(diǎn):
當(dāng)切片的長(zhǎng)度小于數(shù)組長(zhǎng)度(len)時(shí)會(huì) panic。所以上面例子中,s4 和 t1 發(fā)生了 panic 將一個(gè)非空切片轉(zhuǎn)為 0 長(zhǎng)度的數(shù)組,得到的指針不是 nil(如 s0);但將一個(gè)空切片轉(zhuǎn)為 0 長(zhǎng)度的數(shù)組,得到的指針是 nil(如 t0); 多次轉(zhuǎn)換,并不會(huì)創(chuàng)建多個(gè)數(shù)組(因?yàn)榈玫降氖堑讓訑?shù)組),這從 &s2[0] == &s[0]可以看出;
所以,總結(jié)一下就是,將切片轉(zhuǎn)換為數(shù)組指針,產(chǎn)生指向切片的底層數(shù)組的指針。如果切片的長(zhǎng)度小于數(shù)組的長(zhǎng)度,則會(huì)發(fā)生運(yùn)行時(shí) panic。
不過(guò)針對(duì) panic,目前沒(méi)法做斷言檢查。只能通過(guò) if 判斷了。
03 reflect 注意事項(xiàng)
針對(duì)語(yǔ)言這個(gè)改動(dòng),reflect 包中的 Type 接口有一個(gè)方法:ConvertibleTo。之前的說(shuō)明是這樣的:
// 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
因?yàn)榍衅D(zhuǎn)為數(shù)組指針可能會(huì) panic,所以才加了這么一句文檔說(shuō)明。
因此,如果通過(guò)反射轉(zhuǎn)換做類(lèi)型轉(zhuǎn)換,雖然通過(guò) ConvertibleTo 判斷是可轉(zhuǎn)換的,但調(diào)用 Convert 方法依然可能 panic。這點(diǎn)需要特別注意下。
04 小結(jié)
這個(gè)語(yǔ)言改變,大部分時(shí)候可能用不到。但有些場(chǎng)景可以做到不需要內(nèi)存拷貝(copy),比如標(biāo)準(zhǔn)庫(kù)中有一個(gè)例子:
// 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
}
官方計(jì)劃修改為:
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 在修改時(shí),發(fā)現(xiàn),為什么一定要轉(zhuǎn)為數(shù)組指針,能否直接轉(zhuǎn)為數(shù)組,畢竟,在 Go 中使用數(shù)組的話,不太常用數(shù)組指針。于是 bradfitz 給出了另一個(gè)提案:https://github.com/golang/go/issues/46505,即 allow conversion from slice to array。目前該提案是否接受,還沒(méi)有結(jié)論。
