Enums và iota trong Go – Các tip và trick cùng code mẫu.
Chú ý: Trong bài hướng dẫn này, đầu tiên các bạn sẽ học về cách sử dụng enums không có iota – Sau đó, các bạn sẽ tìm hiểu về iota và các công cụ hỗ trợ khác.
Enum là gì?
Enum nhóm các hằng số (constant) có liên quan đến nhau thành cùng một kiểu.
Ví dụ:
- Timezones: EST, CST…
- T-shirt Sizes : Small, Medium, Large
- Server Statuses: Unknown, Running, Stopped, Resumed
Tại sao chúng ta cần sử dụng enum?
- Nhóm và xác định một số giá trị có liên quan đến nhau
- Chia sẻ những hành vi chung
- Tránh sử dụng giá trị không hợp lệ
- Tăng khả năng đọc hiểu và bảo trì code
Làm thế nào để tạo một enum trong Go?
Ví dụ, tạo một enum cho các ngày trong tuần.

♦ Bước 1: Khai báo một kiểu dữ liệu tùy chỉnh có tên là Weekday
Weekday sẽ thống nhất các hằng số enum của chúng ta dưới một kiểu chung.
type Weekday int
♦ Bước 2: Khai báo các hằng số liên quan đến Weekday
Chỉ định cho chúng các giá trị số khác nhau, như vậy chúng sẽ không bị xung đột.
const (
Sunday Weekday = 0
Monday Weekday = 1
Tuesday Weekday = 2
Wednesday Weekday = 3
Thursday Weekday = 4
Friday Weekday = 5
Saturday Weekday = 6
)
// hiển thị số 0
fmt.Println(Sunday)
// hiển thị số 6
fmt.Println(Saturday)
Tạo các hành vi chung cho enum
Chúng ta thêm vào các phương thức (methods) cho một kiểu để xác định hành vi của nó.
Các phương thức được đính kèm sẽ là các phần không thể tách rời của Weekday và được chia sẻ với các hằng số của Weekday.
♦ Phương thức String():
func (day Weekday) String() string {
// khai báo một mảng các string
// toán tử ... để đếm số phần tử
// số phần tử của mảng là (7)
names := [...]string{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"}
// `day`: là một trong các giá trị của hằng số Weekday.
// Nếu hằng số là Sunday, thì day có giá trị là 0.
// Bắt lỗi trong trường hợp `day` nằm ngoài khoảng của Weekday
if day < Sunday || day > Saturday {
return "Unknown"
}
// trả về tên của 1 hằng số Weekday từ mảng names bên trên
return names[day]
}
Let’s try:
fmt.Printf("Which day it is? %s\n", Sunday)
// output: Which day it is? Sunday
Chúng ta đã thêm phương thức String() cho enum Weekday. Thử chạy code để hiểu rõ hơn.
♦ Phương thức Weekend():
func (day Weekday) Weekend() bool {
switch day {
// Nếu day là ngày cuối tuần:
case Sunday, Saturday:
return true
// Nếu day là ngày trong tuần:
default:
return false
}
}
Convention: Thay vì đặt tên là IsWeekend, chúng ta chỉ đặt là Weekend.
Let’s try:
fmt.Printf("Is Saturday a weekend day? %t\n", Saturday.Weekend())
// output: Is Saturday a weekend day? true
Chúng ta đã thêm vào phương thức Weekend cho kiểu Weekday. Thử chạy code.
Tự động tạo tên
Các bạn có thể sử dụng Stringer của Rob Pike để tự động tạo phương thức String.
# install the stringer tool
go get -u -a golang.org/x/tools/cmd/stringer
# grab the code from here
# and save it in weekday.go
# inside your $GOPATH/src/enums
# then, run stringer to create
# enum names automatically
stringer -type Weekday weekdays.go
Bạn cũng có thể sử dụng một số tool khác như: jsonenums (viết bởi Francesc Campoy) hoặc enumer (viết bởi Álvaro López).
Tự động hóa các giá trị enum với iota
- Là một bộ đếm số phổ quát bắt đầu từ 0
- Chỉ được sử dụng với các khai báo constant
- Có thể bị che khuất, nhưng đừng làm thế
Làm thế nào để sử dụng iota?
Biểu thức iota được lặp lại bởi các hằng số cho đến khi có một khai báo kiểu hoặc một phép gán khác xuất hiện.

Để xem iota chạy như thế nào, hãy thử ví dụ trên.
Cách thức hoạt động của iota
iota tăng 1 sau mỗi dòng ngoại trừ dòng trống và chú thích.
Khi nào không nên sử dụng iota
const (
RestartMarkerReply = 110
ServiceReadyInNMinutes = 120
CommandOK = 200
CommandNotImplemented = 202
// ...
)
Không sử dụng iota cho danh sách các giá trị được xác định trước ví dụ như FTP server status codes.
const (
Fatal = iota
)
Ở trường hợp này ta chỉ gán 0. Nó vô hại nhưng không cần thiết.
Biểu thức iota và các nguyên tắc

Biểu thức cơ bản
type Timezone int
const (
// iota: 0, EST: -5
EST Timezone = -(5 + iota)
// iota: 1, CST: -6
CST
// iota: 2, MST: -7
MST
// iota: 3, MST: -8
PST
)
Bạn có thể sử dụng bất kỳ biểu thức liên tục nào với iota.
Resetting iota
// iota reset: it will be 0.
const (
Zero = iota // Zero = 0
One // One = 1
)
// iota reset: will be 0 again
const (
Two = iota // Two = 0
)
// iota: reset
const Three = iota // Three = 0
Bất cứ khi nào một từ khóa const xuất hiện thì iota reset về 0.
Bỏ qua một số giá trị
type Timezone int
const (
// iota: 0, EST: -5
EST Timezone = -(5 + iota)
// _ được gọi là blank identifier
// iota: 1
_
// iota: 2, MST: -7
MST
// iota: 3, MST: -8
PST
)
Bạn có thể bỏ qua một số dòng với blank identifier _, nhưng iota sẽ tiếp tục tăng.
Iota khi chạy qua comment và dòng trống
type Timezone int
const (
// iota: 0, EST: -5
EST Timezone = -(5 + iota)
// Trên 1 comment hoặc 1 dòng trống
// thì iota sẽ không tăng
// iota: 1, CST: -6
CST
// iota: 2, MST: -7
MST
// iota: 3, MST: -8
PST
)
Iota sẽ không tăng khi chạy qua các comment hoặc các dòng trống.
Sử dụng iota ở giữa
const (
One = 1
Two = 2
// Three = 2 + 1 => 3
// iota in the middle
Three = iota + 1
// Four = 3 + 1 => 4
Four
)
Iota có thể được sử dụng trên bất kỳ dòng nào bên trong khai báo const. Ở đây nó là 3 thay vì 0.
Nhiều iota trên cùng một dòng
const (
// Active = 0, Moving = 0,
// Running = 0
Active, Moving, Running = iota, iota, iota
// Passive = 1, Stopped = 1,
// Stale = 1
Passive, Stopped, Stale
)
Trong cùng một dòng, tất cả các hằng số sẽ nhận được cùng một giá trị iota.
const (
// Active = 0, Running = 100
Active, Running = iota, iota + 100
// Passive = 1, Stopped = 101
Passive, Stopped
// Bạn không thể khai báo như thế này.
// Biểu thức sau cùng sẽ được lặp lại
CantDeclare
// Nhưng bạn có thể reset biểu thức như sau
Reset = iota
// Bạn có thể sử dụng biểu thức khác không có iota
AnyOther = 10
)
Bằng cách sử dụng các biểu thức mới, bạn có thể dừng biểu thức lặp lại trước đó.
Lặp lại và hủy biểu thức
const (
// iota: 0, One: 1 (type: int64)
One int64 = iota + 1
// iota: 1, Two: 2 (type: int64)
// Two sẽ được khai báo là:
// Two int64 = iota + 1
Two
// iota: 2, Four: 4 (type: int32)
Four int32 = iota + 2
// iota: 3, Five: 5 (type: int32)
// Five sẽ được khai báo là:
// Five int32 = iota + 2
Five
// (type: int)
Six = 6
// (type: int)
// Seven sẽ được khai báo là:
// Seven = 6
Seven
)
Biểu thức và kiểu được sử dụng sau cùng sẽ được lặp lại.
Bạn có thể nhóm các hằng số với các biểu thức khác nhau.
Chẵn và lẻ
type Even bool
const (
// 0 % 2 == 0 ==> Even(true)
a = Even(iota % 2 == 0)
// 1 % 2 == 0 ==> Even(false)
b
// 2 % 2 == 0 ==> Even(true)
c
// 3 % 2 == 0 ==> Even(false)
d
)
Toán tử `==` chuyển đổi biểu thức iota thành kiểu bool.
Đếm ngược
const (
max = 10
)
const (
a = (max - iota) // 10
b // 9
c // 8
)
Go stdlib text scanner cũng sử dụng patterm này.
Xuất ra bảng chữ cái
const (
// hàm string sẽ chuyển đổi biểu thức thành chuỗi
//
// hoặc nó sẽ gán mã ký tự
a = string(iota + 'a') // a
b // b
c // c
d // d
e // e
)
Các toán tử thao tác bit
type Month int
const (
// 1 << 0 ==> 1
January Month = 1 << iota
February // 1 << 1 ==> 2
March // 1 << 2 ==> 4
April // 1 << 3 ==> 8
May // 1 << 4 ==> 16
June // ...
July
August
September
October
November
December
// Chuỗi iota được break tại đây.
// AllMonths sẽ chỉ có giá trị của tháng được gán,
// không có giá trị của iota.
AllMonths = January | February |
March | April | May | June |
July | August | September |
October | November |
December
)
Với phép bình phương, đoạn code này tạo ra một hằng số bằng tổng cả hằng số trước nó.
Bạn không cần phải sử dụng iota cho mọi hằng số trong một enum, bạn có thể ghi đè lên các giá trị theo cách thủ công.
Cẩn thận với zero-value
type Activity int
const (
Sleeping = iota
Walking
Running
)
func main() {
var activity Activity
}
0 là zero-value đối với kiểu integer. Vì vậy, bạn không thể biết liệu Activity được khởi tạo hay không; Liệu nó có thực sự ở trạng thái Sleeping không?
Đoạn code này sử dụng giá trị iota bắt đầu từ 0.
Mẹo iota + 1
const (
Sleeping = iota + 1
Walking
Running
)
func main() {
var activity Activity
// activity có giá trị 0,
// vì thế nó chưa được khởi tạo
activity = Sleeping
// giờ thì activity đã được khởi tạo
}
Sử dụng “iota + 1” để chắc chắn rằng kiểu enum được khởi tạo.
Đoạn code này sử dụng giá trị iota bắt đầu từ 1
Mẫu trạng thái Unknow (Unknown state pattern)
const (
Unknown = iota
Sleeping
Walking
Running
)
Bắt đầu bằng “Unknow” để chắc chắn enum được khởi tạo.
Đoạn code này sử dụng giá trị Unknow mặc định.
► Trong bảng chữ cái Hy Lạp, iota là chữ cái thứ chín và là chữ cái nhỏ nhất (về mặt biểu diễn trực quan). Phát âm như thế này. Nó đại diện cho một số lượng rất nhỏ của một cái gì đó.
[[ Trong toán vector, và trong một số ngôn ngữ lập trình, nó được sử dụng để tạo ra một mảng các số nguyên liên tiếp như 1, 2, 3, 4… ]]
♦ Nếu bạn tò mò, bạn có thể đọc thêm về việc implementation các hằng số và iota trong mã nguồn Go. Iota được khai báo ở đây là const iota = 0, implementation constant ở đây, và hành vi tăng iota là ở đây.
• • •
- Iota được nhắc tới trong cuốn sách của Iverson: A Programming Language (APL) năm 1962.
- Iota được mượn từ APL bởi một trong những người sáng tạo ra Go, Ken Thompson.
- Một cuộc thảo luận để làm cho iota hoạt động với các biến trong Go 2.
Bài viết được dịch từ blog.learngoprogramming.com
Bình luận