Go defer 去掉閉包函數(shù),靠譜嗎?
大家好,我是煎魚。
在 Go 語言里,defer 關(guān)鍵字是大家很愛用的。因?yàn)樗兄?defer+recover+panic 的組合拳打法,還有種各種 defer close 等常用場(chǎng)景。
這是 Go 語言開發(fā)者必知必會(huì)的編程姿勢(shì)。
defer 常見用法
在語法上,Go defer 的代碼示例如下:
package?main
import?"fmt"
func?main()?{
?defer?fmt.Println("煎魚你好!")
?fmt.Println("放學(xué)別走")
}
輸出結(jié)果:
放學(xué)別走
煎魚你好!
那 defer 在 Go 里的常見用法有哪些呢?首先是上文用到的,直接 defer + 函數(shù):
defer?f()
其次是 defer+閉包的方式:
defer?func()?{?
???result?:=?f()
???//?do?something?with?result
}()
其他還有在面試題上常被考究的傳參變形:
func?f1()?int?{
?i?:=?1
?defer?func()?{
??i++
?}()
?...
}
func?f2()?int?{
?i?:=?1
?defer?func(i?int)?{
??i++
?}(i)
?....
}
這些代碼看起來,我們總是在對(duì) defer 做閉包的各種聲明和使用。defer 會(huì)不會(huì)就是和閉包天生一對(duì)?
新提案:defer 代碼塊
最近大家也在討論一個(gè)與之相關(guān)的 Go 提案《proposal: Go 2: deferred code blocks[1]》,由 @Damien Lloyd 提出,想看看有沒有機(jī)會(huì)把 defer 的新語法落地。

原作者在使用 defer 時(shí)也是經(jīng)常:
defer?f()
但這樣就無法獲得返回值。最終要變成:
defer?func()?{?
???result?:=?f()
???//?do?something?with?result
}()
基于上述類似的原因,想引入如下具有 defer 作用的代碼塊語法:
defer?{
???//?在封閉函數(shù)的末尾執(zhí)行此操作
}
在使用了 defer 關(guān)鍵字的函數(shù)最后執(zhí)行這整個(gè)代碼塊 {...}。代碼塊中的每一行將按順序運(yùn)行。
作者給出的代碼示例:
func?fn()?{
????f,?err?:=?os.Create("eddycjy.txt")
????if?err?!=?nil?{
????????panic(err)
????}
????defer?{
???????err?:=?f.Close()
???????if?err?!=?nil?{
??????????panic(err)
???????}
????}
}
在 fn 函數(shù),聲明了 defer {...},代碼塊內(nèi)是對(duì) f.Close 的兜底判斷和異常拋出。在函數(shù)結(jié)束后執(zhí)行這整個(gè)代碼塊。
反對(duì)的聲音
當(dāng)然,這看著似乎是比較美好的。看起來原提案作者只是簡(jiǎn)化了 defer 是的閉包使用,調(diào)整了作用域的范圍。
但在社區(qū)內(nèi)其實(shí)遭受比較多的反對(duì)聲音。包含但不限于以下幾點(diǎn):
1、收益比不高:這個(gè)提案只是避免了 func() 和 () 等閉包聲明,但是卻要增加新的 defer 語法(語言語法更改會(huì)帶來高昂成本),這個(gè)變更的 ROI 不高。
2、破壞兼容性:原 defer 關(guān)鍵字調(diào)用總是會(huì)跟著函數(shù)的詞法調(diào)用,有良好的一致性。如果進(jìn)行修改,會(huì)產(chǎn)生新的隱晦,破壞一致性。也會(huì)對(duì)現(xiàn)有的許多工具(例如:靜態(tài)分析工具)產(chǎn)生影響,全要改。
3、作用域問題:原本 defer func{}() 的代碼塊結(jié)構(gòu)下,你的代碼作用域都限于閉包函數(shù)下。而使用新的 defer {} 的結(jié)構(gòu),該返回和操作,是否會(huì)影響到外部函數(shù)的結(jié)果?(這是最有爭(zhēng)議的一點(diǎn),作者也比較前言不搭后語,沒明確指明語法意思)
總結(jié)
一開始乍一眼一看,感覺只是把 defer 關(guān)鍵字語句簡(jiǎn)化一下,好像特別好,省了幾個(gè)單詞。就像 if err != nil 也會(huì)有提要用 Rust 的 ? 等用法來替代的。
經(jīng)過社區(qū)網(wǎng)友們指出后,發(fā)現(xiàn)這里貓膩不少。一門已經(jīng)有 10+ 年的編程語言,還有 Go1 兼容性保障的。做出這類帶作用域的提案變更,是有比較大的風(fēng)險(xiǎn)的。
同時(shí)對(duì)于 Go 工具鏈的影響,也是非常大的。一改,直接都完?duì)僮恿恕4_實(shí)需要盡量深思。原作者完全沒提到。
該提案,我寫的時(shí)候正在開放 3 周等待意見收集。很神奇,沒更多的人說話,但提案的表情給了很多個(gè)不認(rèn)同。

推薦閱讀
- Go 團(tuán)隊(duì)將修改 for 循環(huán)變量的語義,Go1.21 新版本即可體驗(yàn)!
- Go1.21 速覽:Context 可以設(shè)置取消原因和回調(diào)函數(shù)了,等的可太久了!
- Go1.21 速覽:過了一年半,slices、maps 泛型庫終于要加入標(biāo)準(zhǔn)庫。。。
參考資料 [1]
proposal: Go 2: deferred code blocks: https://github.com/golang/go/issues/38520
關(guān)注和加煎魚微信,
一手消息和知識(shí),拉你進(jìn)技術(shù)交流群??

你好,我是煎魚,出版過 Go 暢銷書《Go 語言編程之旅》,再到獲得 GOP(Go 領(lǐng)域最有觀點(diǎn)專家)榮譽(yù),點(diǎn)擊藍(lán)字查看我的出書之路。
日常分享高質(zhì)量文章,輸出 Go 面試、工作經(jīng)驗(yàn)、架構(gòu)設(shè)計(jì),加微信拉讀者交流群,和大家交流!
