<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go for 循環(huán)有時候真的很坑。。。

          共 2941字,需瀏覽 6分鐘

           ·

          2023-02-10 20:28

          每天9點準(zhǔn)時發(fā)文 文末送福利 喜歡給個星標(biāo)


          送書活動 已經(jīng)截止,已聯(lián)系前13名同學(xué)領(lǐng)取獎勵啦!喜不喜歡。

          昨天熊哥沒有放福利,今天給大家加碼,6個紅包封面和10元獎金,夠不夠豐厚?文末點擊抽獎。

          今天拜讀一下煎魚的文章。

          不知道有多少 Go 的面試題和泄露,都和 for 循環(huán)有關(guān)。今天我在周末認(rèn)真一看,發(fā)現(xiàn)了 redefining for loop variable semantics[1] ,看來大家踩到的坑都是一樣的。

          著名的硬核大佬 Russ Cox 表示他一直在研究這個問題,表示十年的經(jīng)驗表明了當(dāng)前語義的代價是很大的,得動一動,看看能不能打破兼容性原則。

          想了下之前 Go modules 的事情,我真怕他一口氣就把這塔給推了...

          問題

          案例一

          在 Go 語言中,我們寫 for 語句時有時會出現(xiàn)運行和猜想的結(jié)果不一致。例如以下第一個案例的代碼:

          var all []*Item
          for _, item := range items {
           all = append(all, &item)
          }

          這段代碼有問題嗎?變量 all 內(nèi)的 item 變量,存儲進(jìn)去的是什么?是每次循環(huán)的 item 值,每次都不一樣,對嗎?

          實際上在 for 循環(huán)時,每次存入變量 all 的都是相同的 item,也就是最后一個循環(huán)的 item 值。這是 Go 面試?yán)锝?jīng)常出現(xiàn)的題目,結(jié)合 goroutine 更風(fēng)騷,畢竟還會存在亂序執(zhí)行等問題。

          如果你想解決這個問題,就需要把程序改寫成如下:

          var all []*Item
          for _, item := range items {
           item := item
           all = append(all, &item)
          }

          要重新聲明一個局部變量 item 變量,把 for 循環(huán)的 item 變量給存儲下來,再追加進(jìn)去。

          案例二

          接下來是第二個案例的代碼:

          var prints []func()
          for _, v := range []int{123} {
           prints = append(prints, func() { fmt.Println(v) })
          }
          for _, print := range prints {
           print()
          }

          這段程序的輸出結(jié)果是什么?沒有 & 取地址符,是輸出 1,2,3 嗎?

          結(jié)果程序一運行,輸出結(jié)果是 3,3,3。這又是為什么?

          問題的重點之一:關(guān)注到閉包函數(shù),實際上所有閉包都打印的是相同的 v,也就是輸出 3,原因是在 for 循環(huán)結(jié)束后,最后 v 的值被設(shè)置為了 3,僅此而已。

          如果想要達(dá)到預(yù)期的效果,依然是使用萬能的再賦值。改寫后的代碼如下:

          for _, v := range []int{123} {
            v := v
            prints = append(prints, func() { fmt.Println(v) })
           }

          增加 v := v 語句,程序輸出結(jié)果為 1,2,3。仔細(xì)翻翻你寫過的 Go 工程,是不是都很熟悉?就這改造方法,贏了。

          尤其是配合上 Goroutine 的寫法,很多同學(xué)會更容易在此翻車。

          解決方案

          修復(fù)思路

          實際上 Go 核心團(tuán)隊在內(nèi)部和社區(qū)已經(jīng)討論過許久,希望重新定義 for 循環(huán)的語義。要達(dá)到的目的是:使循環(huán)變量每次迭代而不是每次循環(huán)

          解決的辦法是:在每個迭代變量 x 的每個循環(huán)體開頭,加一個隱式的再賦值,也就是 x := x,就能夠解決上述程序中所隱含的坑。

          和我們現(xiàn)在做的一樣,只不過我們是自己手動加的,Go 團(tuán)隊做的是希望在編譯器內(nèi)隱式處理。

          讓用戶自己決定

          比較尷尬的是 Go 團(tuán)隊在 Proposal: Go 2 transition[2] 中明確禁止重新定義語言的語義,所以 rsc 不能直接這么干。

          因此 rsc 打算開個新坑,希望將會由用戶自己決定控制這個 “破壞”,方式將會是根據(jù)每個 modules 的 go.mod 文件中的 go 行(版本聲明)來決定語義

          例如,如果是在 Go1.30 對本文討論的 for 循環(huán)將循環(huán)變量改為迭代,那么在 go.mod 文件中的 go 版本聲明是將是一個關(guān)鍵的開關(guān)。

          如下圖示:

          像上圖的配置,Go 1.30 或更高版本將會每次迭代變量,而早期 Go 版本的將每次循環(huán)變量,也就是 go.mod 的 Go 版本控制了新特性的語義,不同 modules 都可能會因此不一樣。

          如此一來上述提到的 for 循環(huán)問題都會在一定范圍內(nèi)被解決。

          總結(jié)

          for 循環(huán)時的變量問題,一直是各大 Go 考官愛考的題目,也確實在實際編程 Go 代碼時會遇到這類坑。

          雖然 rsc 希望在 go.mod 文件上開創(chuàng)先河,利用 go 版本的聲明,去修改語義(不允許添加和刪除)。這無疑是給 Go1 兼容性保障開了一個后門。

          如果實施,本次變更會導(dǎo)致 Go 的前后版本語義有所不同。還不如變成一個 go.mod 文件的一個語義開關(guān),一變?nèi)儯駝t這種變一些不變一些的,會給問題排查和理解上帶來不少的成本。

          這顯然是一個很折騰人的思考題。

          推薦閱讀

          參考資料

          [1]

          redefining for loop variable semantics: https://github.com/golang/go/discussions/56010

          [2]

          Proposal: Go 2 transition: https://github.com/golang/proposal/blob/master/design/28221-go2-transitions.md#language-changes


          你好,我是小熊,是一個愛技術(shù)但是更愛錢的程序員。上進(jìn)且佛系自律的人。喜歡發(fā)小秘密/臭屁又愛炫耀。

          奮斗的大學(xué),激情的現(xiàn)在。賺了錢買了房寫了書出了名。當(dāng)過面試官,帶過徒弟搬過磚。

          大廠外來務(wù)工人員。是我,我是小熊,是不一樣的煙火歡迎圍觀。

          關(guān)注我加Go群、加大廠群學(xué)習(xí)交流一起進(jìn)步!
          10元紅包,6個微信紅包封面
          瀏覽 21
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  sese99sese | 草逼,com | 搞逼网址| 中文字幕国产在线观看 | 国产精品一区二区三区高潮 |