讓 iota 從 a +1 開始增量《這是一種誤導,是一種錯誤邏輯》

cheche發表於2019-10-07

我在網上看到了一篇《十條有用的 GO 技術》其實是很多篇,該死的爬蟲。

下面的內容都是誤導別人的錯誤邏輯,可能是原創作者當時入坑不深,我發表此文章的本意僅僅是為了討論技術,可能會使某人不開心,但一個正確的技術答案,是所有人想要的。

其中第六條這樣解釋:

6. 讓 iota 從 a +1 開始增量

在前面的例子中同時也產生了一個我已經遇到過許多次的 bug。假設你有一個新的結構體,有一個 State 欄位:

type T struct {
Name  string
Port  int
State State
}

現在如果基於 T 建立一個新的變數,然後輸出,你會得到奇怪的結果 (http://play.golang.org/p/LPG2RF3y39) :

func main() {
t := T{Name: "example", Port: 6666}
// prints: "t {Name:example Port:6666 State:Running}"
fmt.Printf("t %+vn", t)
}

看到 bug 了嗎?State 欄位沒有被初始化,Go 預設使用對應型別的零值進行填充。由於 State 是一個整數,零值也就是 0,但在我們的例子中它表示 Running。
如何知道 State 被初始化了?如何得知是在 Running 模式?事實上,沒有很好的辦法區分它們,並且它們還會產生更多未知的、不可預測的 Bug。不過,修復這個很容易,只要讓 iota 從 +1 開始 (http://play.golang.org/p/VyAq-3OItv):

const (
Running State = iota + 1
Stopped
Rebooting
Terminated
)

現在 t 變數將預設輸出 Unknown,是不是?

func main() {
t := T{Name: "example", Port: 6666}
// 輸出: "t {Name:example Port:6666 State:Unknown}"
fmt.Printf("t %+vn", t)
}

不過讓 iota 從零值開始也是一種解決辦法。例如,你可以引入一個新的狀態叫做 Unknown,將其修改為:

const (
Unknown State = iota 
Running
Stopped
Rebooting
Terminated
)

OK,上面的內容都是誤導別人的錯誤邏輯,可能是原創作者當時入坑不深,我發表此文章的本意僅僅是為了討論技術,可能會使某人不開心,但一個正確的技術答案,是所有人想要的。

先放一段上面的程式碼

type T struct {
    Name  string
    Port  int
    State State
}

type State int

const (
    Running State = iota //iota將const常量依次繫結了數值0,1,2,3
    Stopped
    Rebooting
    Terminated
)
//我們這裡重寫了string官方函式,
//使得State型別的列印結果不是01234... 而是指定的字串。
func (s State) String() string {
    switch s {
    case Running:
        return "Running"
    case Stopped:
        return "Stopped"
    case Rebooting:
        return "Rebooting"
    case Terminated:
        return "Terminated"
    default:
        return "Unknown"
    }
}
func main() {
    t := T{Name: "example", Port: 6666}
    fmt.Printf("t %+v\n", t)
}

//t {Name:example Port:6666 State:Running}

我們看一下有疑問的地方 main 裡的 fmt.Printf("t %+v\n", t)

t 結構體的第三個欄位沒有賦值,會預設賦予零值。
但是為什麼T.State列印出來是Running,這是正確的,請看我的解釋:

❌有人會這樣想:結果是出乎意料的,T.State的列印結果應該是0。因為State 是一個整數,零值也就是 0,不應該是Running。。。。這樣的邏輯思想是錯誤的。

✅我的結論:(nil != nil 也是這樣的邏輯)

這是因為我們重寫了string函式,在switch判斷中,Running和T.State兩者的型別和值都一樣,所以判斷條件成立,相當於 nameA int 1 == nameB int 1。 在string函式中,我們判斷的是State型別,它與常量和結構體無關。

解決:

1️⃣可以使用標準庫的string列印:fmt.Printf("t %#v\n", t)

2️⃣或者我們去掉自定義的string方法即可。

3️⃣或者修改string方法的接收器

4️⃣或者修改T.State型別

✅總之,在string判斷中,只要型別和值匹配成功,則一定會呼叫重構的string。

✅stackoverflow論壇的回答:(我提問這個問題之後,被很多人鄙視我。。。)

T.State的型別為State。這與欄位T無關。State不是T(struct)的一部分,僅僅是成員。
T.State型別的零值為0,也等於Running。
因此,當您分配給T的例項且T.State未初始化時,T.State會得到零值,即
Running

✅github,GO官方人員回答:(有幸得到了官方的回覆)

就像@DisposaBoy所說的,這是因為State的任何整數值在列印時都會被字串化%+v,在這種情況下,設定了State的零值,所以Running將其列印出來。參見https://yourbasic.org/golang/iota/

最初的時候也是最苦的時候,最苦的時候也是最酷的時候。《轉自SmauelL》

相關文章