Go には列挙型が存在しない
Java などに存在する列挙型ですが、Go にはそのような仕様がありません。
Go では列挙的な実装をしたいときに Iota 識別子が使用できるようです。
今回はシンプルなタスクのステータスを列挙する実装を例に Iota 識別子を使用する方法を考えます。
Iota 識別子
Go には Iota (イオタと読むらしい) という識別子があります。
言語仕様
It can be used to construct a set of related constants
言語仕様にもこのように説明があるので、列挙的な実装をしたいときに使うことが想定されていると受け取れます。
Iota 識別子を使用した列挙的な実装
どのような実装が可能なのか、いくつか試してみます。
定数に int 型の値を設定する
実装
package main
type TaskStatus int
const (
notStarted TaskStatus = iota
inProgress
done
)
func main() {
println("notStarted: ", notStarted)
println("inProgress: ", inProgress)
println("done: ", done)
}
実行結果
go run ./main.go
notStarted: 0
inProgress: 1
done: 2
0を最初としてインクリメントされた整数が定数の値として設定されていることが分かります。
定数に設定する値を0以外の任意の整数にする
実装
先ほどのコードに少し変更を加えます。
package main
type TaskStatus int
const (
notStarted TaskStatus = iota + 1
inProgress
done
)
func main() {
println("notStarted: ", notStarted)
println("inProgress: ", inProgress)
println("done: ", done)
}
実行結果
go run ./main.go
notStarted: 1
inProgress: 2
done: 3
先ほどとは異なり、1を最初としてインクリメントされた整数が定数の値として設定されていることが分かります。
定数に float64 型の値を設定する
float64 の値を設定することもできるようです。
実装
package main
type TaskStatus float64
const (
notStarted TaskStatus = iota
inProgress
done
)
func main() {
println("notStarted: ", notStarted)
println("inProgress: ", inProgress)
println("done: ", done)
}
実行結果
go run ./main.go
notStarted: +0.000000e+000
inProgress: +1.000000e+000
done: +2.000000e+000
int 型の値を設定したとき同様、インクリメントされた値が設定されていることが分かります。
定数に設定する数値に意味がある場合、Iota 識別子は使用してはならない
いくつかの実装を試して、Iota 識別子により定数にインクリメントされた値を設定できることが分かりました。
このインクリメントされた値ですが、意味を持つことは危険です。
先ほどの例を振り返ります。
package main
type TaskStatus int
const (
notStarted TaskStatus = iota + 1
inProgress
done
)
func main() {
println("notStarted: ", notStarted)
println("inProgress: ", inProgress)
println("done: ", done)
}
この実装により設定される整数に以下のように意味を持たせ、整数をアプリケーションロジックで扱うことを想定します。
- タスクステータス
1
= NOT STARTED - タスクステータス
2
= IN PROGRESS - タスクステータス
3
= DONE
ここでタスクステータスに新しい概念「PENDING」を追加したくなった際はどうでしょう。
新しい概念「PENDING」を扱うために以下の実装に変更しました。
package main
type TaskStatus int
const (
notStarted TaskStatus = iota + 1
inProgress
pending
done
)
func main() {
println("notStarted: ", notStarted)
println("inProgress: ", inProgress)
println("pending: ", pending)
println("done: ", done)
}
実行してみます。
go run ./main.go
notStarted: 1
inProgress: 2
pending: 3
done: 4
定数 done
の値が 3
から 4
に変わることでタスクステータス3は DONE ではなくなりました。
タスクステータス 3
= DONE としてアプリケーションロジックで扱っていた場合、アプリケーションロジックでこの変更に対応しない限りバグを生む可能性があります。
Iota 識別子は列挙的な実装が可能になる便利な言語仕様ですが、こういったリスクを意識して適切に使っていきたいですね。