大家好,我是煎魚,
Go1.17rc1 在前幾天終于正式發布了:
看到 Go1.17 增加了一個新特性,是面向 Go 構建時的構建約束的增強,認真一看,是一個時隔 3 年的提案了,原本還在 Go2 和 Go1 之間左右搖擺,這下在 6 月底 Russ Cox 就輸出了新草案:《Bug-resistant build constraints — Draft Design》,
緊接著直接計劃在 Go1.17 發布了,一氣呵成,真實版高效能人士了,
如下圖:
之前小咸魚有遇到好幾個朋友,在報錯時壓根不知道 Go 有這個約束語法,以為只是個單純的注釋,直接不明所以然,感覺科普之路任重道遠,
今天這篇文章煎魚就來講講構建約束這事,
注:下個月 Go1.17 就會正式發布,距離 Go1.18 泛型出山只差一點點距離了,值得期待!
構建約束的背景
簡單來講,在真實環境中,可能需要為不同的編譯環境撰寫不同的 Go 代碼,所以需要做構建約束,
劃重點,Go 語言對這一問題的解決方案是在檔案層面進行有條件的編譯:每個檔案要么在編譯中,要么不在,
也就是,假設不符合構建約束的場景,那么會直接不編譯這個檔案,因為他不在編譯范圍內,那在程式想運行時就會報錯,表示找不到檔案,因此有許多的同學看著報錯資訊,經常找不著北...
現有的構建約束
既然是叫 “增強”,說明現有就有構建約束,最早的構建約束是在 2011 年 9 月引入的構建約束,
我們平時常見的構建約束(build constraint),也叫做構建標記(build tag),構建約束必須出現在 package 之前,
平時會在 Go 工程的檔案中的最開始會看到如下行注解:
// +build
為了將構建約束與包檔案區分開來,構建約束后必須跟一個空行,
// +build linux,386 darwin,!cgo
又或是:
// +build linux darwin
// +build amd64
還可以根據 Go 版本來約束:
// +build go1.9
其主要支持如下幾種:
指定編譯的作業系統,例如:windows、linux 等,對應
runtime.GOOS的值,指定編譯的計算機架構,例如:amd64、386,對應
runtime.GOARCH的值,指定使用的編譯器,例如:gccgo、gc,
指定 Go 版本,例如:go1.9、go1.10 等,
指定自定義的標簽,例如:編譯時通過指定
-tags傳入的值,...
有什么問題
既要用動他,本著 Go team 的 less is more 原則,想必是現有的構建約束,存在著什么問題,才需要調整他,
對語法困惑
從 issues 的反饋來看,是太復雜,如下:
// +build linux,386 darwin,!cgo
他表達的構建約束是:(linux AND 386) OR (darwin AND (NOT cgo)) ,
感覺可以像三元運算子一樣玩出花,可參見《Go 憑什么不支持三元運算子?》,這更夸張,沒常見的邏輯符,
也可以更復雜一些:
// +build 386 !gccgo,amd64 !gccgo,amd64p32 !gccgo
會導致會混淆用戶的認知,如果能夠這樣寫更好:
// +build 386 amd64 amd64p32
// +build !gccgo
對布局困惑
現在的 // +build 有硬性的使用規則:
必須出現在檔案頂部附近,前面只能有空行和其他行注釋,這些規則意味著在 Go 檔案中,構建約束必須出現在 package 子句之前,
為了將構建約束與包檔案區分開來,一系列構建約束后必須跟一個空行,
像是以下失敗案例:
package main
// +build linux
又或是:
/*
Copyright ...
*/
// +build linux
package main
整體來看,官方在 2020 年 3 月對 // +build 注解的使用情況進行了分析,得出以下幾種常見情況:
忽略了
/* */注釋后的構建約束,通常是著作權宣告,忽略了檔案注釋中的構建約束,
忽略了包宣告后的構建約束,
這些都是實際專案中出現的,也就是這個構建約束的布局約束并不好,造成了很多意外和反饋,
像是著作權宣告的統一寫入,基本都是腳本統一打上去的,會造成大量的隱藏版本 BUG,
增強后的構建約束
增強,就是優化,主要的目標之一是解決語法、布局困惑,
設計的核心思想:用新的 //go:build 取代目前用于構建標簽選擇的 //+build,并且使用更廣為熟悉的布爾運算式,
設計的關鍵:平滑過渡,避免破壞 Go 代碼,
以前老的注解:
// +build linux
// +build 386
新的注解:
//go:build linux && 386
新的語法主體為 Go spec 的 EBNF 標記:
BuildLine = "//go:build" Expr
Expr = OrExpr
OrExpr = AndExpr { "||" AndExpr }
AndExpr = UnaryExpr { "&&" UnaryExpr }
UnaryExpr = "!" UnaryExpr | "(" Expr ")" | tag
tag = tag_letter { tag_letter }
tag_letter = unicode_letter | unicode_digit | "_" | "."
也就是說,構建標記的語法與其當前形式保持不變,但構建標記的組合現在使用 Go 的 ||、&& 和 ! 運算子和括號完成,
另外一個檔案只能有一行構建陳述句,也就是一個檔案有多行 //go:build 是錯誤的,如此設計的目的是為了消除關于多行是隱式 AND 還是 OR 在一起的混淆,
過渡階段
在過渡階段,也就是 Go1.17 起,官方的 gofmt 工具會自動根據舊語法轉換新版的語法,以保證兼容性,
例如:
// +build !windows,!plan9
會轉變為:
//go:build !windows && !plan9
// +build !windows,!plan9
后面是計劃把 //+build 給完全下線的,
常規 Go 工程基本用不到,因此就不進一步展開描述了,
對過渡階段感興趣的可以看看 《Bug-resistant build constraints — Draft Design》的 Transition 部分,比較長,正常來講是不需要我們關注的,
總結
Go 1.17 構建約束的增強,一下子讓整個語法明確了起來,統一為 //go:build,至少不會有人看到 //+build 又以為是普通注釋了,
你是否有在作業中遇到構建的版本、環境約束等場景呢,歡迎大家在評論區留言交流!
關注煎魚,吸取他的知識 ????

你好,我是煎魚,高一折騰過前端,參加過國賽拿了獎,大學搞過 PHP,現在整 Go,在公司負責微服務架構等相關作業推進和研發,
從大學開始靠自己賺生活費和學費,到出版 Go 暢銷書《Go 語言編程之旅》,再到獲得 GOP(Go 領域最有觀點專家)榮譽,點擊藍字查看我的出書之路,
日常分享高質量文章,輸出 Go 面試、作業經驗、架構設計,加微信拉讀者交流群,記得點贊!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/289906.html
標籤:其他
上一篇:科普:淘寶網的反爬蟲變遷史
