go1.13
1.對數(shù)字字面量進(jìn)行了改動
在1.13版本之前的golang僅支持十進(jìn)制和十六進(jìn)制的字面量,而其他語言使用廣泛的二進(jìn)制和八進(jìn)制卻不能支持例如下面代碼就無法編譯:
? ? fmt.Println(0b101)
? ? fmt.Println(0o10)
在go1.13中上述字面量語法已經(jīng)被支持了你可以通過0b或0B前綴來表明一個(gè)二進(jìn)制數(shù)字的字面量所森,以及用0o和0O來表明八進(jìn)制字面量返劲。值得注意的是雖然兩種寫法都可以哄啄,但是gofmt默認(rèn)會全部轉(zhuǎn)換為小寫
1.13以下版本運(yùn)行則會出現(xiàn)以下錯誤信息:
? ? # command-line-arguments
? ? usercode/file.go:6: syntax error: unexpected name, expecting )
? ? usercode/file.go:7: syntax error: unexpected name, expecting )? ?
在1.13版本以前的版本并不支持通過0b或0B來表明一個(gè)二進(jìn)制數(shù)字的字面量厨疙,甚至用0o和0O來表明八進(jìn)制字面量也是同樣不支持的
最后對于數(shù)字字面量還有一個(gè)小小的改進(jìn)舆驶,那就是現(xiàn)在可以用下劃線分隔數(shù)字增加可讀性。
? ? fmt.Println(100000000)
? ? fmt.Println(1_0000_0000)
? ? fmt.Println(0xff_ff_ff)
1.13返回結(jié)果:
100000000
100000000
16777215
分隔符可以出現(xiàn)在任意位置赵颅,但是像0x之類的算是一個(gè)完整的符號的中間不可以插入下劃線胶果,分隔符之間字符的數(shù)量沒有規(guī)定必須相等,但為了可讀性最好按照現(xiàn)有的習(xí)慣每3個(gè)數(shù)字或四個(gè)數(shù)字進(jìn)行一次分隔
1.13以下版本運(yùn)行則會出現(xiàn)以下錯誤信息:
# command-line-arguments
./code.go:9: syntax error: unexpected _0000_0000, expecting comma or )
./code.go:10: syntax error: unexpected _ff_ff, expecting comma or )
在1.13版本以前的版本并不支持通過下劃線分割數(shù)字來增加可讀性
2.越界索引報(bào)錯的完善(運(yùn)行時(shí)改進(jìn))
首先golang對數(shù)組和slice的越界引用是0容忍的谈况,一旦越界就會panic勺美,例如下面例子
package main
import "fmt"
func main() {
? ? ? ? arr := [...]int{1,2,3,4,5}
? ? ? ? for i := 0; i <= len(arr); i++ {
? ? ? ? ? ? ? ? fmt.Println(arr[i])
? ? ? ? }
}
1.13以下版本運(yùn)行則會出現(xiàn)以下錯誤信息:
1
2
3
4
5
panic: runtime error: index out of range
1.13以前版本panic并不會把越界的值輸出出來,雖然調(diào)用堆棧信息追溯起來不是很困難,可以方便得定位問題,但如果調(diào)用鏈較深或者你處于一個(gè)高并發(fā)程序之中碑韵,事情就變得麻煩了赡茸,要么我們?nèi)罩菊{(diào)試并最終分析排除大量雜音來定位問題,要么依賴斷點(diǎn)進(jìn)行單步調(diào)式祝闻,無論哪種都需要耗費(fèi)大量的精力而核心問題只是我們想到為什么會越界占卧,再淺一步遗菠,我們有時(shí)候或許只要知道導(dǎo)致越界的值就可以大致確定問題的原因,遺憾的是panic提供的信息中不包含上述內(nèi)容,直到golang1.13华蜒。而現(xiàn)在golang會將導(dǎo)致越界的值打印出來辙纬,無疑是雪中送碳:
1
2
3
4
5
panic: runtime error: index out of range [5] with length 5
goroutine 1 [running]:
main.main()
? ? ? ? /Users/chengaosheng/go/test/test.go:46 +0xec
exit status 2
當(dāng)然,panic信息再完善也不是靈丹妙藥叭喜,完善的單元測試和嚴(yán)謹(jǐn)?shù)墓ぷ鲬B(tài)度才是bug最好的預(yù)防針贺拣。
3.工具鏈改進(jìn)
除了去除了godoc程序,最大的變化仍舊集中在go modules上捂蕴。
?3.1 GOPROXY
其實(shí)這個(gè)變量在1.12中就引入了譬涡,這次為其加上了默認(rèn)值https://proxy.golang.org,direct, 這是一個(gè)逗號分隔的列表啥辨,后面兩個(gè)變量的值和它相同涡匀,其中direct表示不經(jīng)過代理直接連接,如果設(shè)置為off溉知,則進(jìn)制下載任何package陨瘩。
在go get等命令獲取package時(shí),會從左至右依次查找着倾,如果都沒有找到匹配的package拾酝,則會報(bào)錯。
proxy的好處自然不用多說卡者,它可以使國內(nèi)開發(fā)者暢通無阻地訪問某些國內(nèi)環(huán)境無法獲取的包。更重要的是默認(rèn)的proxy是官方提供和維護(hù)的客们,比起第三方方案來說安全性有了更大的保障崇决。
?3.2 GOSUMDB
這個(gè)變量實(shí)際上相當(dāng)于指定了一個(gè)由官方管理的在線的go.sum數(shù)據(jù)庫。具體介紹之前我們先來看看golang是如何驗(yàn)證packages的:
go get下載的package會根據(jù)go.mod文件和所有下載文件分別建立一個(gè)hash字符串底挫,存儲在go.sum文件中恒傻;
下載的package會被cache,每次編譯或者手動go mod verify時(shí)會重新計(jì)算與go.sum中的值比較建邓,出現(xiàn)不一致就會報(bào)安全錯誤盈厘。
這個(gè)機(jī)制是建立在本地的cache在整個(gè)開發(fā)生命周期中不會變動之上的(因?yàn)橐蕾噹斓陌姹竞苌贂M(jìn)行更新,除非出現(xiàn)重大安全問題)官边,上述機(jī)制可以避免他人誤更新依賴或是本地的惡意篡改沸手,然而現(xiàn)在更多的安全問題是發(fā)生在遠(yuǎn)程環(huán)境的,因此這一機(jī)制有很大的安全隱患注簿。
好在加入了GOSUMDB契吉,它的默認(rèn)值為“sum.golang.org”,國內(nèi)部分地區(qū)無法訪問诡渴,可以改為“sum.golang.google.cn”【杈В現(xiàn)在的工作機(jī)制是這樣的:
go get下載包并計(jì)算校驗(yàn)和,計(jì)算好后會先檢查是否已經(jīng)出現(xiàn)在go.sum文件中,如果沒有則去GOSUMDB中檢查惑灵,校驗(yàn)和一致則寫入go.sum文件山上;否則報(bào)錯
如果對應(yīng)版本的包的校驗(yàn)和已經(jīng)在go.sum中,則不會請求GOSUMDB英支,其余步驟和舊機(jī)制一樣佩憾。
安全性得到了增強(qiáng)。
3.3 GOPRIVATE
最后要介紹的是GOPRIVATE潭辈,默認(rèn)為空鸯屿,你可以在其中使用類似Linux glob通配符的語法來指定某些或某一類包不從proxy下載,比如某些rpc套件自動生成的package把敢,這些在proxy中并不會存在寄摆,而且即使上傳上去也沒有意義,因此你需要把它寫入GOPRIVATE中修赞。
還有一個(gè)與其類似的環(huán)境變量叫GONOPROXY婶恼,值的形式一樣,作用也基本一樣柏副,不過它會覆蓋GOPRIVATE勾邦。比如將其設(shè)為none時(shí)所有的包都會從proxy進(jìn)行獲取。
4.標(biāo)準(zhǔn)庫的新功能
每次新版本發(fā)布都會給標(biāo)準(zhǔn)庫帶來大把的新功能新特性割择,這次也不例外眷篇。
4.1 判斷變量是否為0值
golang中任何類型的0值都有明確的定義,然而遺憾的是不同的類型0值不同荔泳,特別是那些自定義類型蕉饼,如果你要判斷一個(gè)變量是否0值那么將會寫出復(fù)雜繁瑣而且擴(kuò)展困難的代碼。
因此reflect中新增了這一功能簡化了操作:
package main
import (
? ? ? ? "fmt"
? ? ? ? "reflect"
)
func main() {
? ? ? ? a := 0
? ? ? ? b := 1
? ? ? ? c := ""
? ? ? ? d := "a"
? ? ? ? fmt.Println(reflect.ValueOf(a).IsZero()) // true
? ? ? ? fmt.Println(reflect.ValueOf(b).IsZero()) // false
? ? ? ? fmt.Println(reflect.ValueOf(c).IsZero()) // true
? ? ? ? fmt.Println(reflect.ValueOf(d).IsZero()) // false
}
當(dāng)然玛歌,反射一勞永逸的代價(jià)是更高的性能消耗昧港,所以具體取舍還要參照實(shí)際環(huán)境。
4.2 錯誤處理的革新
其實(shí)算不上革新支子,只是對現(xiàn)有做法的小修小補(bǔ)创肥。golang團(tuán)隊(duì)始終有覺得error既然是值那就一定得體現(xiàn)value的equal操作的怪癖,所以整體上還是很怪值朋。
首先要介紹錯誤鏈(error chains)的概念叹侄。
在1.13中,我們可以給error實(shí)現(xiàn)一個(gè)Unwrap的方法吞歼,從而實(shí)現(xiàn)對error的包裝圈膏,比如:
type PermError {
? ? ? ? os.SyscallError
? ? ? ? Pid uint
? ? ? ? Uid uint
}
func (err *PermError) String() string {
? ? ? ? return fmt.Sprintf("permission error:\npid:%v\nuid:\ninfo:%v", err.Pid, err.Uid, err.SyscallError)
}
func (err *PermError) Error() string {
? ? ? ? return err.String()
}
// 重點(diǎn)在這里
func (err *PermError) Unwrap() error {
? ? ? ? return err.SyscallError
}
假設(shè)我們包裝了一個(gè)基于SyscallError的權(quán)限錯誤,包括了所有因?yàn)闄?quán)限問題而觸發(fā)的error篙骡。String和Error方法都是常規(guī)的自定義錯誤中會實(shí)現(xiàn)的方法稽坤,我們重點(diǎn)看Unwrap方法丈甸。
Unwrap字面意思就是去包裝,也就是我們把包裝好的上一層錯誤重新分離出來并返回尿褪。os.SyscallError也實(shí)現(xiàn)了Unwrap睦擂,于是你可以繼續(xù)向上追溯直達(dá)最原始的沒有實(shí)現(xiàn)Unwrap的那個(gè)error為止。我們稱從PermError開始到最頂層的error為一條錯誤鏈杖玲。
如果我們用→指向Unwrap返回的對象顿仇,會形成下面的結(jié)構(gòu):
PermError → os.SyscallError → error
還可以出現(xiàn)更復(fù)雜的結(jié)構(gòu):
A → Err1 ___________
|
V
B → Err2 → Err3 → error
這樣無疑提升了錯誤的表達(dá)力,如果不想自己單獨(dú)定義一個(gè)錯誤類型摆马,只想附加某些信息臼闻,可以依賴fmt.Errorf:
newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr)
sysErr == newErr.(interface {Unwrap() error}).Unwrap()
fmt.Errorf新的占位符%w只能在一個(gè)格式化字符串中出現(xiàn)一次,他會把error的信息填充進(jìn)去囤采,然后返回一個(gè)實(shí)現(xiàn)了Unwrap的新error述呐,它返回傳入的那個(gè)error。另外提案里的Wrapper接口目前還沒有實(shí)現(xiàn)蕉毯,但是標(biāo)準(zhǔn)庫用了我在上面的做法暫時(shí)實(shí)現(xiàn)了Wrapper的功能乓搬。
因?yàn)殄e誤鏈的存在,我們不能在簡單的用等于號基于判斷基于值的error了代虾,但好處是我們現(xiàn)在還可以判斷基于類型的error进肯。
為了能繼續(xù)讓error表現(xiàn)自己的值語義,errors包里增加了Is和As以及輔助它們的Unwrap函數(shù)棉磨。
Unwrap
errors.Unwrap會調(diào)用傳入?yún)?shù)的Unwrap方法江掩,As和Is使用它來追溯整個(gè)錯誤鏈。
像上一小節(jié)的代碼就可以簡化成這樣:
newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr)
sysErr == errors.Unwrap(newErr).Unwrap()
我們提到等于號的比較很多時(shí)候已經(jīng)不管用了乘瓤,有的時(shí)侯一個(gè)error只是對另一個(gè)的包裝频敛,當(dāng)這個(gè)error產(chǎn)生時(shí)另一個(gè)也已經(jīng)發(fā)生了,這時(shí)候我們只需要比較處于上層的error值即可馅扣,這時(shí)候你就需要errors.Is幫忙了:
newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr)
errors.Is(newErr, sysErr)
errors.Is(newErr, os.ErrExists)
你永遠(yuǎn)也不知道程序會被怎樣擴(kuò)展,也不知道error之間的關(guān)系未來會怎樣變化着降,因此總是用Is代替==是不會犯錯的差油。
不過凡事總有例外,例如io.EOF就不需要使用Is去比較任洞,因?yàn)樗绦蛞饬x上算不上是error蓄喇,而且一般也不會有人包裝它。
As
除了傳統(tǒng)的基于值的判斷训桶,對某個(gè)類型的錯誤進(jìn)行處理也是一個(gè)常見需求泛范。例如前文的A圃阳,B都來自error,假設(shè)我們現(xiàn)在要處理所有基于這個(gè)error的錯誤钱骂,常見的辦法是switch進(jìn)行比較或者依賴于基類的多態(tài)能力叔锐。
顯而易見的是switch判斷的做法會導(dǎo)致大量重復(fù)的代碼,而且擴(kuò)展困難见秽;而在golang里沒有繼承只有組合愉烙,所以有運(yùn)行時(shí)多態(tài)能力的只有interface,這時(shí)候我們只能借助錯誤鏈讓errors.As幫忙了:
// 注意As的第二個(gè)參數(shù)只能是你需要判斷的類型的指針解取,不可以直接傳一個(gè)nil進(jìn)去
var p1 *os.SyscallError
var p2 *os.PathError
errors.As(newErr, &p1)
errors.As(newErr, &p2)
如果p1和p2的類型在newErr所在的錯誤鏈上步责,就會返回true,實(shí)現(xiàn)了一個(gè)很簡陋的多態(tài)效果禀苦。As總是用于替代if _, ok := err.(type); ok這樣的代碼蔓肯。
當(dāng)然,上面的函數(shù)一方面讓你少寫了很多代碼振乏,另一方面又嚴(yán)重依賴反射蔗包,特別是錯誤鏈很長的時(shí)候需要反復(fù)追溯多次,所以這里有兩條忠告:
不要過渡包裝昆码,沒什么是加間接層解決不了的气忠,但是中間層太多不僅影響性能也會干擾后續(xù)維護(hù);
如果你實(shí)在在意性能赋咽,而且保證不存在對現(xiàn)有error的擴(kuò)展(例如io.EOF)旧噪,那么使用傳統(tǒng)方案也無傷大雅。
go1.14
?1.工具
1.1.go build等命令默認(rèn)將會使用 -mod=vendor脓匿,如果需要使用mod cache需要顯示指定 -mod=mod淘钟。
1.2.go mod init 設(shè)置go.mod文件是-mod=readonly,go.mod是只讀模式的陪毡。
1.3.go mod tidy之外的go命令不再編輯go.mod文件
1.4.除非明確要求或已經(jīng)要求該版本米母,否則 go get 將不再升級到該模塊的不兼容主要版本。直接從版本控制中獲取時(shí)毡琉,go list 還會忽略此模塊的不兼容版本
1.5.在 module 模式下铁瞒,go 命令支持 SVN 倉庫
1.6.go test -v 現(xiàn)在將 t.Log 輸出流式傳輸,而不是在所有測試數(shù)據(jù)結(jié)束時(shí)輸出
2.goroutine 支持異步搶占
在Go1.1版本中桅滋,調(diào)度器還不支持搶占式調(diào)度慧耍,只能依靠 goroutine 主動讓出 CPU 資源,存在非常嚴(yán)重的調(diào)度問題丐谋。
Go1.12中編譯器在特定時(shí)機(jī)插入函數(shù)芍碧,通過函數(shù)調(diào)用作為入口觸發(fā)搶占,實(shí)現(xiàn)了協(xié)作式的搶占式調(diào)度号俐。但是這種需要函數(shù)調(diào)用主動配合的調(diào)度方式存在一些邊緣情況泌豆,就比如說下面的例子:
func main() {
? ? ? ? runtime.GOMAXPROCS(1)?
? ? ? ? go func() {
? ? ? ? ? ? ? ? for {
? ? ? ? ? ? ? ? }
? ? ? ? }()
? ? ? ? time.Sleep(time.Millisecond)
? ? ? ? println("OK")
}
上面代碼中,其中創(chuàng)建一個(gè)goroutine并掛起吏饿, main goroutine 優(yōu)先調(diào)用了 休眠踪危,此時(shí)唯一的 P 會轉(zhuǎn)去執(zhí)行 for 循環(huán)所創(chuàng)建的 goroutine蔬浙,進(jìn)而 main goroutine 永遠(yuǎn)不會再被調(diào)度。換一句話說在Go1.14之前陨倡,上邊的代碼永遠(yuǎn)不會輸出OK敛滋,因?yàn)檫@種協(xié)作式的搶占式調(diào)度是不會使一個(gè)沒有主動放棄執(zhí)行權(quán)、且不參與任何函數(shù)調(diào)用的goroutine被搶占兴革。
Go1.14 實(shí)現(xiàn)了基于信號的真搶占式調(diào)度解決了上述問題绎晃。Go1.14 程序啟動時(shí), 會在函數(shù)runtime.sighandler 中注冊了 SIGURG 信號的處理函數(shù) runtime.doSigPreempt杂曲,在觸發(fā)垃圾回收的棧掃描時(shí)庶艾,調(diào)用函數(shù)掛起goroutine,并向M發(fā)送信號擎勘,M收到信號后咱揍,會讓當(dāng)前goroutine陷入休眠繼續(xù)執(zhí)行其他的goroutine。
3.testing 包新增CleanUp 方法
testing包的T棚饵、B和TB都加上了CleanUp方法煤裙,它將以后進(jìn)先出的方式執(zhí)行 f(如果注冊多個(gè)的話)。
如下代碼噪漾,輸出結(jié)果是 test cleanup硼砰, clear resource:
func TestCleanup(t *testing.T) {
? t.Cleanup(func() {
? ? ? t.Log("clear resource")
? })
? t.Log("test cleanup")
}
#### 4.允許嵌入具有重疊方法集的接口
下面接口定義在 Go1.14 之前是不允許的
type ReadWriteCloser interface {
? ? io.ReadCloser
? ? io.WriteCloser
}
因?yàn)?io.ReadCloser 和 io.WriteCloser 中 Close 方法重復(fù)了,編譯時(shí)會提示:duplicate method Close欣硼。在Go1.14中支持了這種重復(fù)接口集
go1.15
1. 1.15版本變化
1.1.對Go鏈接器的實(shí)質(zhì)性改進(jìn)
1.2.改進(jìn)了對高核心計(jì)數(shù)的小對象的分配
1.3.X.509 CommonName棄用
1.4.GOPROXY支持跳過返回錯誤的代理
1.5.新增了一個(gè)time/tzdata包
1.6.核心庫的一些改進(jìn)
2. 核心庫的一些改進(jìn)
2.1 time.Ticker增加了一個(gè)Reset方法支持改變ticker的duration题翰。 time.Ticker是一個(gè)周期性的定時(shí)器,內(nèi)置一個(gè)周期性傳遞時(shí)間的Channel诈胜。 使用time.NewTicker(d Duration)函數(shù)創(chuàng)建一個(gè)Ticker豹障,這個(gè)Ticker內(nèi)置一個(gè)通道字段,每個(gè)時(shí)間間隔會向這個(gè)通道發(fā)送當(dāng)前的時(shí)間焦匈。ticker會調(diào)整時(shí)間間隔或者丟棄消息以適應(yīng)反應(yīng)慢的接收者血公。
func TestTickerReset(t *testing.T) {
wait := make(chan struct{})
ticker := time.NewTicker(time.Second * 1)
go func() {
defer close(wait)
for i := 0; i < 5; i++ {
t.Log(<-ticker.C)
if i == 2 {
ticker.Reset(time.Second * 2)
}
}
}()
<-wait
}
2.2 time/tzdata是Go 1.15新增加的包,當(dāng)系統(tǒng)找不到時(shí)區(qū)數(shù)據(jù)時(shí)缓熟,通過導(dǎo)入這個(gè)包坞笙,可以在程序中內(nèi)嵌時(shí)區(qū)數(shù)據(jù)。 導(dǎo)入這個(gè)包會使程序大小增加大約800KB荚虚,注意time/tzdata這個(gè)包應(yīng)該是在程序的main包中導(dǎo)入的,而不要在一個(gè)libary項(xiàng)目中導(dǎo)入和使用籍茧。 另外也可以通過編譯時(shí)傳遞-tags timetzdata來實(shí)現(xiàn)同樣的效果版述。
3.panic展現(xiàn)形式變化
在Go 1.15之前,如果傳給panic的值是bool, complex64, complex128, float32, float64, int, int8, int16, int32, int64, string, uint, uint8, uint16, uint32, uint64, uintptr等原生類型的值寞冯,那么panic在觸發(fā)時(shí)會輸出具體的值渴析,比如:
package main
func foo() {
? ? var i uint32 = 17
? ? panic(i)
}
func main() {
? ? foo()
}
1.15以上版本:
panic: main.myint(27)
goroutine 1 [running]:
main.bar(...)
? ? ? ? /Users/chengaosheng/go/test/test.go:63
main.main()
? ? ? ? /Users/chengaosheng/go/test/test.go:56 +0x39
exit status 2
1.15以下版本:
panic: (main.myint) (0x105fca0,0xc00008e000)
goroutine 1 [running]:
main.bar(...)
? ? /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:12
main.main()
? ? /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:17 +0x39
exit status 2
Go 1.15針對此情況作了展示優(yōu)化晚伙,即便是派生于這些原生類型的自定義類型變量,panic也可以輸出其值俭茧。
4. 標(biāo)準(zhǔn)庫
4.1 增加json解碼限制
json包是日常使用最多的go標(biāo)準(zhǔn)庫包之一咆疗,在Go 1.15中,go按照json規(guī)范的要求母债,為json的解碼增加了一層限制午磁。如果一旦傳入的json文本數(shù)據(jù)縮進(jìn)深度超過maxNestingDepth,那json包就會panic毡们。當(dāng)然迅皇,絕大多數(shù)情況下,我們是碰不到縮進(jìn)10000層的超大json文本的衙熔。因此登颓,該limit對于99.9999%的gopher都沒啥影響。
4.2 reflect包
Go 1.15版本之前reflect包
例子:
package main
import "reflect"
type u struct{}
func (u) M() { println("M") }
type t struct {
? ? u
? ? u2 u
}
func call(v reflect.Value) {
? ? defer func() {
? ? ? ? if err := recover(); err != nil {
? ? ? ? ? ? println(err.(string))
? ? ? ? }
? ? }()
? ? v.Method(0).Call(nil)
}
func main() {
? ? v := reflect.ValueOf(t{}) // v := t{}
? ? call(v)? ? ? ? ? ? ? ? ? // v.M()
? ? call(v.Field(0))? ? ? ? ? // v.u.M()
? ? call(v.Field(1))? ? ? ? ? // v.u2.M()
}
1.15以上版本:
172-15-70-45:test chengaosheng$ go run test.go
M
reflect: reflect.Value.Call using value obtained using unexported field
reflect: reflect.Value.Call using value obtained using unexported field
我們看到reflect無法調(diào)用非導(dǎo)出字段u和u2的導(dǎo)出方法了红氯。但是reflect依然可以通過提升到類型t的方法來間接使用u的導(dǎo)出方法框咙,正如運(yùn)行結(jié)果中的第一行輸出。
**這一改動可能會影響到遺留代碼中使用reflect調(diào)用以類型嵌入形式存在的非導(dǎo)出字段方法的代碼**痢甘,如果你的代碼中存在這樣的問題喇嘱,可以直接通過提升(promote)到包裹類型(如例子中的t)中的方法(如例子中的call(v))來替代之前的方式。
?go1.16
?1.Go 語言所打包的二進(jìn)制文件中會包含配置文件的聯(lián)同編譯和打包
無法將靜態(tài)資源編譯打包進(jìn)二進(jìn)制文件的話产阱,通常會有兩種解決方法:
第一種是識別這類靜態(tài)資源婉称,是否需要跟著程序走。
第二種就是考慮將其打包進(jìn)二進(jìn)制文件中构蹬。
第二種情況的話王暗,Go 以前是不支持的,大家就會去借助各種花式的開源庫庄敛,例如:go-bindata/go-bindata 來實(shí)現(xiàn)俗壹。
但從在 Go1.16 起,Go 語言自身正式支持了該項(xiàng)特性
演示代碼:
import _ "embed"
//go:embed hello.txt
var s string
func main() {
print(s)
}
首先在對應(yīng)目錄下創(chuàng)建hello.txt文件并寫"Hello world"
在代碼中編寫了最為核心的 //go:embed hello.txt 注解藻烤。注解的格式很簡單绷雏,就是 go:embed 指令聲明,外加讀取的內(nèi)容的地址怖亭,可支持相對和絕對路徑涎显。
輸出結(jié)果:
Hello world
讀取到靜態(tài)文件中的內(nèi)容后自動賦值給了變量 s,并且在主函數(shù)中成功輸出兴猩。
而針對其他的基礎(chǔ)類型期吓,Go embed 也是支持的:
//go:embed hello.txt
var s string
//go:embed hello.txt
var b []byte
//go:embed hello.txt
var f embed.FS
func main() {
print(s)
print(string(b))
data, _ := f.ReadFile("hello.txt")
print(string(data))
}
輸出結(jié)果:
Hello world
Hello world
Hello world
我們同時(shí)在一個(gè)代碼文件中進(jìn)行了多個(gè) embed 的注解聲明。
并且針對 string倾芝、slice讨勤、byte箭跳、fs 等多種類型進(jìn)行了打包,也不需要過多的處理潭千,非常便利谱姓。
?2.拓展用法:
除去基本用法完,embed 本身在指令上也支持多種變形:
//go:embed hello1.txt hello2.txt
var f embed.FS
func main() {
data1, _ := f.ReadFile("hello1.txt")
fmt.Println(string(data1))
data2, _ := f.ReadFile("hello2.txt")
fmt.Println(string(data2))
}
在指定 go:embed 注解時(shí)可以一次性多個(gè)文件來讀取刨晴,并且也可以一個(gè)變量多行注解:
//go:embed hello1.txt
//go:embed hello2.txt
var f embed.FS
也可以通過在注解中指定目錄 helloworld屉来,再對應(yīng)讀取文件:
//go:embed helloworld
var f embed.FS
func main() {
data1, _ := f.ReadFile("helloworld/hello1.txt")
fmt.Println(string(data1))
data2, _ := f.ReadFile("helloworld/hello2.txt")
fmt.Println(string(data2))
}
同時(shí)既然能夠支持目錄讀取,也能支持貪婪模式的匹配:
//go:embed helloworld/*
var f embed.FS
embed.FS 也能調(diào)各類文件系統(tǒng)的接口割捅,其實(shí)本質(zhì)是 embed.FS 實(shí)現(xiàn)了 io/fs 接口
3.只讀屬性
在 embed 所提供的 FS 中奶躯,其實(shí)可以發(fā)現(xiàn)都是打開和只讀方法:
? ? type FS
? ? func (f FS) Open(name string) (fs.File, error)
? ? func (f FS) ReadDir(name string) ([]fs.DirEntry, error)
? ? func (f FS) ReadFile(name string) ([]byte, error)
根據(jù)此也可以確定 embed 所打包進(jìn)二進(jìn)制文件的內(nèi)容只允許讀取,不允許變更亿驾。
更抽象來講就是在編譯期就確定了 embed 的內(nèi)容嘹黔,在運(yùn)行時(shí)不允許修改,保證了一致性莫瞬。