爲什麼我們需要在 Go 中使用 iota

圖片拍攝於 2020 年 10 月 25 日,杭州大明山。

介紹

Go 語言實際上沒有直接支持枚舉的關鍵字。一般我們都是通過 const + iota 實現枚舉的能力。

有人要問了,爲什麼一定要使用枚舉呢?stackoverflow[1] 上有一個高讚的回答,如下:

You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: "permanent", "temp", "apprentice"), or flags ("execute now", "defer execution").

If you use enums instead of integers (or String codes), you increase compile-time checking and avoid errors from passing in invalid constants, and you document which values are legal to use.

簡單翻譯一下, 兩點很重要。

如何實現枚舉

iota 是 Go 中預聲明的一個特殊常量。它會被預聲明爲 0,但是它的值在編譯階段並非是固定的,當預聲明的 iota 出現在一個常量聲明中,它的值在第 n 個常量描述中的值爲 n(從 0 開始)。

比如,大家都或多或少了解電商系統。其中的訂單模塊一定會涉及到訂單狀態的流轉。那麼這時候,我們一般可以這樣定義:

package main

import "fmt"

type OrderStatus int

const (
  Cancelled OrderStatus = iota //訂單已取消 0
  NoPay OrderStatus = iota //未支付 1
  PendIng OrderStatus = iota // 未發貨 2
  Delivered OrderStatus = iota // 已發貨 3
  Received OrderStatus = iota // 已收貨 4
)

func main() {
  fmt.Println(Cancelled, NoPay) // 打印:0,1
}

當然,這樣看着好麻煩。其實,其他常量可以重複上一行 iota 表達式,我們可以改成這樣。

package main

import "fmt"

type OrderStatus int

const (
  Cancelled OrderStatus = iota //訂單已取消 0
  NoPay //未支付 1
  PendIng // 未發貨 2
  Delivered // 已發貨 3
  Received // 已收貨 4
)

func main() {
  fmt.Println(Cancelled, NoPay) // 打印:0,1
}

有人會用 0 的值來表示狀態嗎?一般都不會,我們想以 1 開頭,那麼可以這樣。

package main

import "fmt"

type OrderStatus int

const (
  Cancelled OrderStatus = iota+1 //訂單已取消 1
  NoPay //未支付 2
  PendIng // 未發貨 3
  Delivered // 已發貨 4
  Received // 已收貨 5
)

func main() {
  fmt.Println(Cancelled, NoPay) // 打印:1,2
}

我們還想在 Delivered 後跳過一個數字,纔是 Received 的值, 也就是 Received=6,那麼可以藉助 _ 符號。

package main

import "fmt"

type OrderStatus int

const (
  Cancelled OrderStatus = iota+1 //訂單已取消 1
  NoPay //未支付 2
  PendIng // 未發貨 3
  Delivered // 已發貨 4
  _
  Received // 已收貨 6
)

func main() {
  fmt.Println(Received) // 打印:6
}

順着來可以,倒着當然也行。

package main

import "fmt"

type OrderStatus int

const (
  Max = 5
)

const (
  Received OrderStatus = Max - iota // 已收貨 5
  Delivered // 已發貨 4
  PendIng // 未發貨 3
NoPay //未支付 2
  Cancelled //訂單已取消 1
)

func main() {
  fmt.Println(Received,Delivered) // 打印:5,4
}

還可以使用位運算,比如在 go 源碼中的包 sync 中的鎖上面有這麼一段定義代碼。

const (
    mutexLocked =<< iota  //1<<0
    mutexWoken //1<<1
    mutexStarving //1<<2
    mutexWaiterShift = iota  //3

)

func main() {
    fmt.Println("mutexLocked的值",mutexLocked) //打印:1
    fmt.Println("mutexWoken的值",mutexWoken) //打印:2
    fmt.Println("mutexStarving的值",mutexStarving) //打印:4
    fmt.Println("mutexWaiterShift的值",mutexWaiterShift) // 打印:3
}

也許有人平常是直接定義常量值或者用字符串來表示的。

比如,上面這些我完全可以用 string 來表示,我還真見過用字符串來表示訂單狀態的。

package main

import "fmt"

const (
  Cancelled = "cancelled"
  NoPay = "noPay"
  PendIng = "pendIng"
  Delivered = "delivered"
  Received = "received"
)

var OrderStatusMsg = map[string]string{
  Cancelled: "訂單已取消",
  NoPay: "未付款",
  PendIng: "未發貨",
  Delivered: "已發貨",
  Received: "已收貨",
}

func main() {
  fmt.Println(OrderStatusMsg[Cancelled])
}

或者直接定義整形常量值。

package main

import "fmt"

const (
  Cancelled = 1
  NoPay = 2
  PendIng = 3
  Delivered = 4
  Received = 5
)

var OrderStatusMsg = map[int]string{
  Cancelled: "訂單已取消",
  NoPay: "未付款",
  PendIng: "未發貨",
  Delivered: "已發貨",
  Received: "已收貨",
}

func main() {
  fmt.Println(OrderStatusMsg[Cancelled])
}

其實上述兩種都可以,但是相比之下使用 iota 更有優勢。

延伸

按照上面我們所演示的,最後我們可以這樣操作。

package main

import (
  "fmt"
)

type OrderStatus int

const (
  Cancelled OrderStatus = iota + 1 //訂單已取消 1
  NoPay //未支付 2
  PendIng // 未發貨 3
  Delivered // 已發貨 4
  Received // 已收貨 5
)

//公共行爲 賦予類型 String() 函數,方便打印值含義
func (order OrderStatus) String() string {
  return [...]string{"cancelled", "noPay", "pendIng", "delivered", "received"}[order-1]
}

//創建公共行爲 賦予類型 int 函數 EnumIndex()
func (order OrderStatus) EnumIndex() int {
  return int(order)
}

func main() {
  var order OrderStatus = Received
  fmt.Println(order.String()) // 打印:received
  fmt.Println(order.EnumIndex()) // 打印:5
}

總結

這篇文章主要介紹了 Golang 中對 iota 的使用介紹,以及我們爲什麼要使用它。

不知道大家平常對於此類場景用的什麼招數,歡迎下方留言交流。

附錄

[1]https://stackoverflow.com/questions/4709175/what-are-enums-and-why-are-they-useful

https://levelup.gitconnected.com/implementing-enums-in-golang-9537c433d6e2

https://medium.com/qvault/how-and-why-to-write-enums-in-go-9c1a25649df0

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/HOkquS6bA1I18TyT1lR8zQ