Go 為什么不支持從 main 包中導(dǎo)入函數(shù)?
大家好,我是煎魚。
作為一個維護(hù)過許多有一定歷史沉淀的 Go 項目的人,在歷史債務(wù)下和奇葩需求下,會遇到一些迫于業(yè)務(wù)需求的技術(shù)訴求。
訴求上是希望引用多項目,會出現(xiàn)從 main 包(package)中導(dǎo)入相關(guān)函數(shù)的這種使用訴求。為了將多 Go 工程合并到一個大單體中使用。
問題案例
具體的使用案例如下。
我們有一個 Go 應(yīng)用,目錄結(jié)構(gòu)如下:
demo1
├── go.mod
├── main.go
└── x
└── main.go
demo1/x/main.go 文件內(nèi)代碼如下:
package main
import (
"fmt"
)
func main() {
Main()
}
func Main() {
fmt.Println("煎魚進(jìn)水了?")
}
demo1/main.go 文件內(nèi)代碼如下:
package main
import (
"fmt"
xmain "example.com/greet/x" // 也就是本應(yīng)用,上面的 x
)
func main() {
fmt.Println("腦子進(jìn)煎魚了!")
xmain.Main()
}
簡單來講,就是 demo1 這個 Go 項目,擁有兩個 main 包。根目錄下的 main.go 文件內(nèi)引用了 x/main.go 內(nèi)的 Main 方法。
運(yùn)行該程序,看看運(yùn)行結(jié)果:
$ go run main.go
main.go:6:2: import "example.com/greet/x" is a program, not an importable package
會直接報錯,提示 x 包下是一個程序,而不是一個可導(dǎo)入的包。
為什么不支持導(dǎo)入 main 包
這個問題稍微可以收斂一下,關(guān)鍵內(nèi)容是:為什么不支持導(dǎo)入 main 包內(nèi)的函數(shù)?明明 main 包也是一個 package,其個別函數(shù)也是大寫開頭,是允許對外導(dǎo)出的。
我首先翻閱了一下 Go 語言規(guī)范(spec),確實沒有非常明確禁止該項行為。但又確實在我們?nèi)粘J褂煤途幾g運(yùn)行時,會被拒絕運(yùn)行。提示前面的錯誤。
隨后又查看了具體的代碼提交和 CL,實際上在 13 年前。現(xiàn)任 Go 核心團(tuán)度負(fù)責(zé)人 @rsc 是提交過相應(yīng) main 包支持的。
如下 CL 所示:
圖片
2011 年(13 年前)的 CL 移除了原本語言規(guī)范中定義的 “程序中的其他包都不能命名為 main” 的要求,也就是可以滿足前文問題和背景中提到的使用訴求。
看到這里有的同學(xué)就疑惑了。怎么 13 年后的現(xiàn)在,2024 年。又不行了呢?而且感覺是不行好久了。
因為在 2015 年時,現(xiàn)任 Go 核心團(tuán)隊成員 @ianlancetaylor,又又又改了,增加了非常明確的判斷,直接限制了。
如下代碼變更:
圖片
比較有趣的是,@rsc 和 @ianlancetaylor 的變更都是針對同一個 issues #4210:《cmd/go: go build does not reject importing commands》。
怎么后面又變了呢?@ianlancetaylor 給出的明確答復(fù)和定義:
圖片
CL 4126053(原先 @rsc 提交的那次)是對描述語言規(guī)范的修改。該語言允許導(dǎo)入名為 main 的包。例如:在為使用 main 包的命令中的函數(shù)編寫單元測試時,就可以使用它。
但這里的問題是關(guān)于 Go 工具,而不是語言。問題是 go 工具是否應(yīng)該允許軟件包導(dǎo)入定義命令的包。普遍的共識是不應(yīng)該。
所提及的 Go 工具,覆蓋的范圍是:cmd/go。包含了 go build 等相關(guān)命令。因此是在受限制范圍的。
經(jīng)過如此切分場景,就能知道為什么語言規(guī)范上沒有明確禁止。但 Go 工具上又明確拒絕了。因為其對應(yīng)覆蓋了不同的使用場景。
不支持的原因,結(jié)合討論來看。
普遍認(rèn)為支持 main 包的導(dǎo)入,會造成更大的復(fù)雜度和不安全性。
像是在 main 函數(shù)在編寫時,通常會假定自己擁有完全的控制權(quán),因此多個 main 包內(nèi)的函數(shù)引入,可能會造成在 init 函數(shù)的初始化順序、全局變量的注冊等,都會產(chǎn)生程序上的沖突。
總結(jié)
在本次對 Go 工具限制從 main 包中導(dǎo)入相關(guān)函數(shù)的緣由,我們做了詳盡的了解和分析。雖然 Go 官方這樣的方式可以一刀切的解決復(fù)雜度和安全性的問題。
但有歷史沉淀、債務(wù)的情況下,對于需要維護(hù)多個 Go 工程項目,要交付不同種類的可組合項目的程序員來說。相當(dāng)于磨滅了一條道路。還是比較尷尬的。