go特性總結(jié)(1.13~1.16)

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í)不允許修改,保證了一致性莫瞬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末儡蔓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子疼邀,更是在濱河造成了極大的恐慌喂江,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旁振,死亡現(xiàn)場離奇詭異获询,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拐袜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門吉嚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹬铺,你說我怎么就攤上這事尝哆。” “怎么了甜攀?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵秋泄,是天一觀的道長。 經(jīng)常有香客問我规阀,道長恒序,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任谁撼,我火速辦了婚禮奸焙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己与帆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布墨榄。 她就那樣靜靜地躺著玄糟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪袄秩。 梳的紋絲不亂的頭發(fā)上阵翎,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音之剧,去河邊找鬼郭卫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛背稼,可吹牛的內(nèi)容都是我干的贰军。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蟹肘,長吁一口氣:“原來是場噩夢啊……” “哼词疼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帘腹,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贰盗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后阳欲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舵盈,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年球化,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秽晚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赊窥,死狀恐怖爆惧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锨能,我是刑警寧澤扯再,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站址遇,受9級特大地震影響熄阻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜倔约,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一秃殉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦钾军、人聲如沸鳄袍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拗小。三九已至,卻和暖如春樱哼,著一層夾襖步出監(jiān)牢的瞬間哀九,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工搅幅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阅束,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓茄唐,卻偏偏與公主長得像息裸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子琢融,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容