Introduction

The Go language doesn’t actually have direct support for the enumeration keyword. We generally implement enumeration capabilities via const + iota.

One might ask, why do we have to use enums? There is a highly rated answer on stackoverflow.

1
2
3
4
5
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.

BTW, overuse of enums might mean that your methods do too much (it's often better to have several separate methods, rather than one method that takes several flags which modify what it does), but if you have to use flags or type codes, enums are the way to go.

How to implement an enumeration

iota is a special constant that is predeclared in Go. It will be pre-declared as 0, but its value is not fixed at the compile stage. When the pre-declared iota appears in a constant declaration, its value in the nth constant description will be n (starting from 0). So it only seems to make sense in the case of multiple constant declarations of the same type.

For example, we all know about e-commerce, the order system must involve the flow of order status. So this is when we can generally do this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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
}

Of course, this looks like a lot of work. Actually, other constants can repeat the iota expression from the previous line, and we can change it to this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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
}

Would anyone use a value of 0 for the state? Usually not, we want to start with 1, then we can do so.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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
}

We also want to skip a number after Delivered to be the value of Received, i.e. Received=6, then we can use the _ symbol.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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
}

It’s okay to go with the flow, but it’s certainly okay to go backwards.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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
}

You can also use bitwise operations, such as the code above the lock in the package sync in the go source code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const (
    mutexLocked = 1 << 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
}

Maybe some people usually define constant values directly or use strings to represent them.

For example, I could have used string for the above, and I’ve actually seen strings used to represent order status.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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])
}

Or just define shaped constant values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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])
}

Both of the above are actually possible, but using iota is more advantageous in comparison.

  • The uniqueness of a set of constants can be guaranteed, which cannot be guaranteed for manually defined ones.
  • Can share the same behavior for a set of actions.
  • Avoid invalid values.
  • Improves code readability as well as maintenance.

Extensions

Following what we have demonstrated above, we can end up like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
}

Summary

This article introduces the use of iota in Golang and why we should use it.