Go1.20 將禁止匿名介面迴圈匯入!這是一次打破 Go1 相容性承諾的真實案例
最近因為臨近新版本釋出節點,我在看 Go1.20 的新特性《spec: disallow anonymous interface cycles[1]》,發現了一個比較騷的操作...以前我都沒想到可以這麼用,還有點意思,分享給大家。
在 Go 規範中是允許將介面型別(interface{})內嵌到其他宣告的介面當中的,也就是著名的套娃神器:組合。
套娃介面型別
Go 標準庫中比較經典的例子如下:
type ReadCloser interface {
Reader
Closer
}
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
實際上展開是:
type ReadCloser interface {
interface {
Read(p []byte) (n int, err error)
}
interface {
Close() error
}
}
一切都看起來如此美好,似乎很好的體現了 Go 的優秀之處。
計劃是趕不上變化的。
匿名介面迴圈匯入
在現實程式碼中,這種支援就存在著迴圈引用的用法。如下簡單例子:
type I interface {
m()
interface {
I
}
}
這段程式碼,宣告瞭介面型別 I,然後又包含了 m(),又包含介面 I。這會是一個 “永動機”,永遠都不會停止。在開源的 GitHub 中,也真實存在著。
如專案 gozelus/zelus_rest[2] 的程式碼:
type MySQLDb interface {
execSQL
Table(ctx context.Context, name string) interface {
whereSQL
insertSQL
selectSQL
findSQL
orderSQL
clausesSQL
}
Begin() interface {
MySQLDb
Rollback()
Commit()
}
}
如專案 vetcher/go-astra[3] 的程式碼:
type ComplexInterface interface {
A(a interface {
B()
ComplexInterface
}) interface {
C()
D()
}
}
這類寫法其實非常迷惑人,這意味可以無限巢狀介面,並使用內在的方法。但作者在寫這個程式碼時,可能目的並不是如此,導致被使用者錯用。
這有沒有問題
對外宣傳簡潔好用瀑布式程式設計的 Go,如此對匿名介面迴圈匯入的支援,是否合規呢?
其實並不然。
早在 2016 年的 Proposal: Type Aliases[4] 中的 Type cycles 部分就對此有所定義:
在型別別名的提案中明確指出:別名必須能夠 "向外展開",沒有辦法展開出像 T = *T
這樣的型別別名。
套用到現在的問題來,如果上面的 T 就是 I(介面型別),那麼同理可得 I = *I
,這個過程是永遠無法終止的。
社群討論
在一番激烈討論後,基於以下幾點,決定接納該提案,也就是在新版本中禁用 Go 匿名介面的迴圈匯入,將其改為有限地擴充套件所有的嵌入式介面。
在禁用後,以下三種類似寫法都會被拒絕。
第一種:
type B interface { I }
type I interface { m() interface { B } }
第二種:
type B = interface{ I }
type I interface{ m() interface{ B } }
第三種:
type B = interface{ I }
type I interface{ m() B }
Go1 相容性承諾
最核心的是 Go1 相容性承諾。從任何角度上來講,禁用這個特性是破壞性變更(無法向後相容),絕對是違反相容性承諾的。
大家認為在公共專案庫中,基本沒有人使用這種匿名介面迴圈匯入的方式,用途很少(幾乎為 0)除了上面提到的 gozelus/zelus_rest 專案,並且該模組似乎沒什麼人引用。
rsc 在綜合了利弊後,認為把這個特性幹掉,能更好的提高程式碼簡潔性,確立了該特性的禁用,會和以往一樣的推進節奏。
如下:
Go1.20:Go 編譯器預設會拒絕這些介面迴圈,但可以使用 go build -gcflags=all=-d=interfacecycles
來進行構建,以確保舊程式碼的正常編譯。如果在候選釋出期間有人向 Go 團隊報告大量損壞,將會取消此更改。Go1.22:等到 1.22 版本後 -d=interfacecycles
標誌將被刪除,舊程式碼將不再構建該特性。如果有人報告問題,將可以討論或是推遲刪除,給予更多的改造時間。
鏈式呼叫模式
有一種經典的設計模式叫:鏈式呼叫,也有叫方法鏈的。例如在 etcd sdk 中,常常會在 Watch、Next 這類相關介面中見到。
在 Go 中可以這麼寫:
type Nexter interface {
Next(Input) (interface { Nexter }, error)
Done() Output
}
一旦禁用後,就不能如此匿名巢狀了。
會強烈推薦使用如下方式:
type Nexter interface {
Next(Input) (Nexter, error)
Done() Output
}
包括在 Node 這類節點宣告時,也推薦如此:
type Node interface {
Parent() Node
FirstChild() Node
Children() []Node
}
套娃也得套上名字,不能成為 “無名” 者。
總結
原先支援匿名介面的迴圈匯入,本質上違背了 Go 一貫的簡潔明瞭的設計理念。如果在 Go 工程中用的多,不注意就會產生次生影響,禁了也有好處。
目前該特性變更的程式碼已經提交。如果按照 rsc 的計劃我們會在 Go1.20 或 Go1.21 看到這個新特性,Go1.22 或 Go1.24 將會正式移除。
值得關注的一點,Go團隊為此打破了對 Go1 相容性的承諾,做出了破壞性變更,在推進方式上採取的是漸進式的模式。
這仍然值得我們關注,畢竟...破窗效應?
參考資料
spec: disallow anonymous interface cycles:
[2]gozelus/zelus_rest:
[3]vetcher/go-astra:
[4]Proposal: Type Aliases:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2927536/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Go1.20 將禁止匿名介面迴圈匯入!這是一次打破 Go1 相容性承諾的真實案例。。。Go
- Go1.20 將會修改全域性變數的初始化順序。梅開二度,繼續打破 Go1 相容性承諾!Go變數
- 還能這樣玩?Go 將會增強 Go1 向前相容性Go
- 他說遇到了迴圈匯入,但是我怎麼看我的程式碼都沒有迴圈匯入
- Flutter for迴圈案例Flutter
- 如何避免Python的迴圈匯入問題Python
- 這是一次 docker 入門實踐Docker
- Shell實現迴圈執行curl向Solr匯入json檔案SolrJSON
- 【匯入匯出】sqlldr 匯入案例SQL
- 動態步長的for迴圈 ,,, 自從培訓結束 這還是頭一次用到
- python 寫 po 模式時遇到的迴圈匯入問題Python模式
- 一次線上死迴圈的排查
- 【資料泵】EXPDP匯出表結構(真實案例)
- Ubuntu出現一直登入介面迴圈狀況Ubuntu
- Java入門學習-學習if & else,for迴圈,foreach迴圈,while迴圈的用法。JavaWhile
- PostgreSQLOracle相容性-synonym匿名SQLOracle
- goland 匿名匯入包不起作用GoLand
- JavaScript 模組的迴圈載入JavaScript
- 記錄一次 postgresql 最佳化案例( 巢狀迴圈改HASH JOIN )SQL巢狀
- JavaScript的map迴圈、forEach迴圈、filter迴圈、reduce迴圈、reduceRight迴圈JavaScriptFilter
- Java簡單迴圈語句案例Java
- Spring迴圈依賴+案例解析Spring
- 認真看完這篇文章,JVM將不再是你的短板JVM
- 清華尹成帶你實戰GO案例(8)Go for迴圈Go
- 據說這將是一款讓你真實體驗遊戲製作人的遊戲遊戲
- 單向迴圈連結串列的介面程式
- 順序迴圈佇列的介面設計佇列
- java中for迴圈和ArrayList的詳細解析案例Java
- Sql 巢狀迴圈最佳化案例SQL巢狀
- 利用VB 指令碼實現TIA 中介面迴圈計數的功能指令碼
- 關於應該實現ActionListener介面還是使用ActionListener的匿名類物件物件
- java 匿名 泛型 獲取真實類名Java泛型
- 再探迴圈依賴 → Spring 是如何判定原型迴圈依賴和構造方法迴圈依賴的?Spring原型構造方法
- RHEL 6.5 登陸後放回登陸介面,迴圈登入問題
- 設計單向迴圈連結串列的介面
- 固定資產介面匯入
- SQL調優真實案例SQL
- 碎片化學習Java(十六)Java For迴圈案例Java