之前在 Hacker News 看到了一篇讨论 Go Enums 的文章,题目是 Go Enums Suck[1]。我不想对这篇文章发表任何意见,因为如果我们把 Go 的 iota 机制看作一个枚举,那么与其他语言相比,它的实现确实很一般。但本文想讨论下 Go iota 的用法,正确的使用也可以大大提升开发效率。
Golang 没有内置的枚举关键字。一般来说,我们使用 const + iota 来实现定义枚举的功能。有人可能会问,为什么要使用枚举?我认为在 Stack Overflow 上的这个高赞的回答可以解释这个问题:
“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.”
如何实现枚举
iota 是 Go 中一个预先声明的特殊常量。它被预声明为 0,但在编译过程中其值并不固定。当 iota 在常量声明中出现时,它在第 n 个常量描述中的值为 n(从 0 开始)。因此,只有当同一类型有多个常量声明时,它才有意义。
例如,在电子商务系统中,订单状态至关重要。在这种情况下,我们通常可以使用下面的方式:
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota // Order cancelled 0
NoPay // Not paid 1
Pending // Not shipped 2
Delivered // Delivered 3
Received // Received 4
)
func main() {
fmt.Println(Cancelled, NoPay) // Prints: 0, 1
}
其他常量可以重复前面的 iota 表达式。我们可以这样简化:
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota // Order cancelled 0
NoPay // Not paid 1
Pending // Not shipped 2
Delivered // Delivered 3
Received // Received 4
)
func main() {
fmt.Println(Cancelled, NoPay) // Prints: 0, 1
}
可能不会有人从 0 开始计数,我们想从 1 开始,可以这样做:
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota + 1 // Order cancelled 1
NoPay // Not paid 2
Pending // Not shipped 3
Delivered // Delivered 4
Received // Received 5
)
func main() {
fmt.Println(Cancelled, NoPay) // Prints: 1, 2
}
如果我们想跳过 Delivered 后面的数字,将其赋值给 Received,即 Received = 6,我们可以使用 _ 符号。
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota + 1 // Order cancelled 1
NoPay // Not paid 2
Pending // Not shipped 3
Delivered // Delivered 4
_
Received // Received 6
)
func main() {
fmt.Println(Received) // Prints: 6
}
我们也可以倒序进行。
package main
import "fmt"
type OrderStatus int
const (
Max = 5
)
const (
Received OrderStatus = Max - iota // Received 5
Delivered // Delivered 4
Pending // Not shipped 3
NoPay // Not paid 2
Cancelled // Order cancelled 1
)
func main() {
fmt.Println(Received, Delivered) // Prints: 5, 4
}
我们甚至可以使用按位运算,例如以下来自 Go 源代码中同步软件包的代码片段,它处理的是锁:
const (
mutexLocked = 1 << iota // 1<<0
mutexWoken // 1<<1
mutexStarving // 1<<2
mutexWaiterShift = iota // 3
)
func main() {
fmt.Println("Value of mutexLocked:", mutexLocked) // Prints: 1
fmt.Println("Value of mutexWoken:", mutexWoken) // Prints: 2
fmt.Println("Value of mutexStarving:", mutexStarving) // Prints: 4
fmt.Println("Value of mutexWaiterShift:", mutexWaiterShift) // Prints: 3
}
有些人可能会直接定义常量值或使用字符串。例如,我见过用字符串值表示订单状态的情况。
package main
import "fmt"
const (
Cancelled = "cancelled"
NoPay = "noPay"
Pending = "pending"
Delivered = "delivered"
Received = "received"
)
var OrderStatusMsg = map[string]string{
Cancelled: "Order cancelled",
NoPay: "Not paid",
Pending: "Not shipped",
Delivered: "Delivered",
Received: "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: "Order cancelled",
NoPay: "Not paid",
Pending: "Not shipped",
Delivered: "Delivered",
Received: "Received",
}
func main() {
fmt.Println(OrderStatusMsg[Cancelled])
}
这两种方法都可以,但使用 iota 比其他方法更有优势:
-
确保一组常量的唯一性,手动定义的常量无法保证这一点。 -
允许一组操作共享相同的行为。 -
避免无效值。 -
提高代码的可读性和可维护性
扩展
在上述示范的基础上,我们可以进一步扩展:
package main
import (
"fmt"
)
type OrderStatus int
const (
Cancelled OrderStatus = iota + 1 // Order cancelled 1
NoPay // Not paid 2
Pending // Not shipped 3
Delivered // Delivered 4
Received // Received 5
)
// Define a common behavior by assigning the type a String() function for easy printing of value meanings
func (order OrderStatus) String() string {
return [...]string{"cancelled", "noPay", "pending", "delivered", "received"}[order-1]
}
// Create a common behavior by assigning the type an EnumIndex() function of type int
func (order OrderStatus) EnumIndex() int {
return int(order)
}
func main() {
var order OrderStatus = Received
fmt.Println(order.String()) // Prints: received
fmt.Println(order.EnumIndex()) // Prints: 5
}
结论
本文主要介绍 iota 在 Go 中的用法,并解释为什么我们应该使用它。任何编程语言的设计都有不完美的地方,我们需要适应它。
Go Enums Suck: https://www.zarl.dev/articles/enums
原文始发于微信公众号(Go Official Blog):Go 的 iota 并非枚举
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/295156.html