你能答對這道 Go 題目嗎?超過 80% 的人都答錯了...
大家好,我是煎魚。
最近在沖浪的時候,看到 Redhat 首席軟件工程師、Prometheus 等項目的維護(hù)者 Bart?omiej P?otka 在 twitter 上出了一道 Go 的 “考題”,說是要來考考大家。

題目如下:
func?aaa()?(done?func(),?err?error)?{
?return?func()?{?
???print("aaa:?done")?
?},?nil
}
func?bbb()?(done?func(),?_?error)?{
?done,?err?:=?aaa()
?return?func()?{?
???print("bbb:?surprise!");?
???done()?
?},?err
}
func?main()?{
?done,?_?:=?bbb()
?done()
}
答案的選項如下:
想想原因,思考一下輸出結(jié)果是什么?是 A,還是 D?還是三短一長,選 B?
分析程序
縮小范圍,核心關(guān)注到這塊代碼。如下:
func?bbb()?(done?func(),?_?error)?{
?done,?err?:=?aaa()
?return?func()?{?
???print("bbb:?surprise!")
???done()?
?},?err
}
在最后一行的這個閉包(匿名函數(shù))中,大家可能認(rèn)為程序調(diào)用了函數(shù) aaa 所返回的 done 值來輸出程序,應(yīng)當(dāng)是:
aaa:?done
這個想法是錯誤的,程序沒有這么去運(yùn)作。
原因在于 return 實(shí)際上是一個賦值語句。結(jié)合程序,可以看到函數(shù) bbb 的第一個返回值是 done 參數(shù)。
如下:
func?bbb()?(done?func(),?_?error)
也就是在函數(shù) bbb 在程序最后執(zhí)行 return 語句后,會對返回變量 done 進(jìn)行賦值,自然該值不會是由函數(shù) aaa 所設(shè)置的了。
這是一個關(guān)鍵的地方。
具體過程
這個程序輸出結(jié)果是什么呢?
他會不斷地遞歸,瘋狂輸出 “bbb: surprise!”,直至棧溢出,導(dǎo)致程序運(yùn)行出錯,最終中止
同學(xué)就疑惑了,怎么又多出了個遞歸?
我們再看看程序:
func?main()?{
?done,?_?:=?bbb()
?done()
}
func?bbb()?(done?func(),?_?error)?{
?...
?return?func()?{?
???print("bbb:?surprise!");?
???done()?
?},?err
}
本質(zhì)上在函數(shù) bbb 執(zhí)行完畢后,?變量 done 已經(jīng)變成了一個遞歸函數(shù)。
遞歸的過程是:函數(shù) bbb 調(diào)用變量 done 后,會輸出 bbb: surprise! 字符串,然后又調(diào)用變量 done。而變量 done 又是這個閉包(匿名函數(shù)),從而實(shí)現(xiàn)不斷遞歸調(diào)用和輸出。
最終結(jié)果如下:
b:?surprise!bbb:?surprise!bbb:?surprise!runtime:?goroutine?stack?exceeds?1000000000-byte?limit
runtime:?sp=0xc0200e0380?stack=[0xc0200e0000,?0xc0400e0000]
fatal?error:?stack?overflow
runtime?stack:
runtime.throw(0x1074b5a,?0xe)
????????/usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:1117?+0x72
runtime.newstack()
????????/usr/local/Cellar/go/1.16.6/libexec/src/runtime/stack.go:1069?+0x7ed
runtime.morestack()
????????/usr/local/Cellar/go/1.16.6/libexec/src/runtime/asm_amd64.s:458?+0x8f
...
也就是正確答案是:D,程序最終運(yùn)行出錯。
一直調(diào)用一直爽,直至棧溢出程序崩潰。
總結(jié)
這位大佬出的題目,本質(zhì)上是比較煩人的,其結(jié)合了函數(shù)返回參數(shù)的命名用法。
如果我們把這個函數(shù)的返回參數(shù)命名去掉,就可以避開這個問題。如下:
func?bbb()?(func(),?error)?{
?...
?return?func()?{?
???print("bbb:?surprise!");?
???done()?
?},?err
}
...
輸出結(jié)果為 "bbb: surprise!"。
很多 Go 的同學(xué)在日常代碼編寫的時候不會用到或注意到。但如果寫的時候有類似案例代碼中的模式,就會排查許久都查不到。
這是個有警惕意義的題目,你覺得呢?
參考
twitter 上的原題目 Go 語言返回值是引用時return后省略是推薦寫法嗎?
關(guān)注煎魚,獲取業(yè)內(nèi)第一手消息和知識 ??

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