ryota21silvaの技術ブログ

Funna(ふんな)の技術ブログ

これまで学んだ技術の備忘録。未来の自分が救われることを信じて

【Go】Enumを扱う

Enum(列挙型)とは

列挙型とは、プログラミング言語やデータベース管理システムなどにおけるデータ型の一つで、複数の異なる定数を一つの集合として定義するもの。多くの言語では “enum” の略号で示される。 列挙型(集合型)とは - 意味をわかりやすく - IT用語辞典 e-Words

とあるように、複数の定数を1つのクラス(型)としてまとめて管理できるもの。

Javaであれば

public enum Fruits{
    ORANGE,
    APPLE,
    BANANA
};

のように書けるみたい。 引数や戻り値の型をEnumに限定できるのでプログラムの堅牢性が増すし、可読性が上がったりする。

qiita.com

www.modis.co.jp

GoでEnumを扱うには?

GoにはJavaにおけるEnum(列挙型)のような機能が無い。
そのため、const定義とその中に iota (※1)を使うことでEnumを扱うのが一般的?かもしれない。

ただし、Goは変数を初期化する際に明示的に値を代入しない場合に、デフォルトで割り振られる値が決まっているので注意。
これをZero Value(ゼロ値)(※2)といい、int型の場合は0で、string型の場合は“”で初期化されるようになっている。

※1: iotaとは定数宣言(const)内で使用される識別子のことで、0から始まる型なしの整数連番を生成してくれる。
※2: Zero Value(ゼロ値)については、ゼロ値を使おう #golang - Qiitaが分かりやすい。



この仕様から、Goでは以下のように定数の割り当てを1からにすればいい。

type Fruit int

const (
    ORANGE Fruit = iota + 1 // 1
    APPLE                   // 2
    BANANA                  // 3
)

以下のfruits := Fruits{}は明示的に値を代入していないので、Zero Valueの仕様により、fruits.Fにはint型のデフォルト値の0が入ってしまう。

そのため iota + 1としておけば、意図した挙動になる。

type Fruits struct {
    F Fruit
}

func main() {
    fruits := Fruits{} // 変数初期化時に値を代入していないので、0が割り振られる

    switch fruits.F {
    case ORANGE:
        fmt.Println(“オレンジ”) // (+1していないと、この分岐を通ってしまう)
    case APPLE:
        fmt.Println(“りんご”)
    case BANANA:
        fmt.Println(“バナナ”)
    default:
        fmt.Println(“エラー”) // この分岐を通ってくれる
    }
}

公式パッケージの例

Goの標準パッケージtimeに用意されている構造体time.Time型を返すtime.Date関数は第2引数にtime.Month型を求めており、

func Date(year int, month Month, day int, hour int, min int, sec int, nsec int, loc *Location) Time

以下のようにtime.Month型はconst定義と iota を使って表現されており、これによって引数の型をEnumであるtime.Monthに限定しようとしていることが分かる(intをベタ書きしてもコンパイル通るとは思うけど)。

(time/time.go)
// A Month specifies a month of the year (January = 1, ...).
type Month int

const (
    January Month = 1 + iota
    February
    March
    April
    May
    June
    July
    August
    September
    October
    November
    December
)

実際使う場合はこんな感じ。

time.Date(2022, time.August, 10, 12, 0, 0, 0, time.UTC)

参考記事