Edited at

protocol buffers ENUM型 完全ガイド


protobufのEnum型について

https://developers.google.com/protocol-buffers/docs/proto3#enum

protocol buffersで定義できる型の一つに、enumがある。

"Enum"という要素の実装は、プログラミング言語ごとに違うだろう。高機能な型チェックができる場合もあれば、そもそも言語に概念自体がないこともある。protocプラグインで吐き出される実装がどうなるかはちゃんと確認したほうがいい。

その上でprotocol buffers上はどういうモノを"Enum"と呼んでいるのかまとめる。構文はこんな感じ。

syntax = "proto3";

package myapp;

enum Season {
SEASON_UNKNOWN = 0;
SPRING = 1;
SUMMER = 2;
AUTUMN = 3;
WINTER = 4;
}


  • 列挙した値のいずれかであることを要求する型である

  • 実体はint32の範囲の数字のどれかを選ぶ


    • 負値でもOK

    • 文字列をenumにすることはできない。実体は必ず数字。




  • 必ず、最初にデフォルト値の 0 を定義する必要がある


    • 0が不要だから定義しない、というのは許されない

    • 0は別にunknown(不明値)という意味にしなくても良い


      • が、考えるのが面倒でよくunknown扱いにしがちである



    • 参考: gRPCステータスコードの0OKとして使われている。エラーコードのようなものだと0を成功=エラーなしの意味にするとわかりやすい



  • 対応するJSONの表現では "SPRING"1 のどちらでも認識する


    • ちゃんと文字列部分もコードとして吐き出されている




enumの制約

enumのラベルは、その空間で一意にしなくてはならない。以下はコンパイルエラーである。

syntax = "proto3";

package myapp;

enum Season {
UNKNOWN = 0;
//...
}

enum Weekday {
UNKNOWN = 0; // protocがコンパイルエラーを起こす
//...
}

仕方がないので、適当にprefixをつけてごまかすことになる。

syntax = "proto3";

package myapp;

enum Season {
SEASON_UNKNOWN = 0;
//...
}

enum Weekday {
WEEKDAY_UNKNOWN = 0;
//...
}

名前の宣言場所が違うなら、別に同じラベルを使っても構わない。


  • パッケージ

  • messageの中に入れ子で宣言されている

syntax = "proto3";

package myapp;

enum Season {
UNKNOWN = 0;
//...
}

message Calendar {
enum Weekday {
UNKNOWN = 0; //入れ子になっているので、これならコンパイルが通る
//...
}
// ...
}

prefixを常に付けていると、enumのラベルがどんどん長くなってしまう。適当にパッケージやメッセージを分割し、短い命名ができるように工夫すると良い。


Goの場合の出力

https://developers.google.com/protocol-buffers/docs/reference/go-generated#enum

Goの場合の出力を見てみる。

冒頭の例で書いたSeasonというenumだとこんな感じになる。

type Season int32

const (
Season_SEASON_UNKNOWN Season = 0
Season_SPRING Season = 1
Season_SUMMER Season = 2
Season_AUTUMN Season = 3
Season_WINTER Season = 4
)

//...

func (x Season) String() string {
return proto.EnumName(Season_name, int32(x))
}

Goにenumという型はないが、enum相当のことをやりたいときによく使われるイディオムを踏襲している。

String()メソッドによって、fmt.Stringerインターフェースが実装されていることになる。fmt.Println()などを使って出力すると数字ではなくSPRINGなどの文字列が出力される。(定数名はSeason_SPRINGだが、表示される文字列はSPRINGである)

パッケージトップレベルのenumの場合、こんな風に型名_ラベルという名前の定数が定義されていく。なお、messageの中に入れ子にenumを定義した場合、親message名_ラベル という名前で定数が定義されていく。


その他のオプション


deprecated

「廃止予定」を表すマーク。enumに限らずあらゆるところで書けるオプションだが、enumの中でも書ける。

enum Season {

option deprecated = false; // enum型自体のdeprecatedを表す
SEASON_UNKNOWN = 0;
SPRING = 1;
SUMMER = 2 [deprecated = false]; //要素単体のdeprecatedを表す
AUTUMN = 3;
WINTER = 4;
}


allow_alias

同じ数字に対して複数のラベルをつけることができるようにする。

enum Season {

option allow_alias = true;
SEASON_UNKNOWN = 0;
SPRING = 1;
SUMMER = 2;
AUTUMN = 3;
FALL = 3; // 3は既に使われてるけど、オプションによってエラーにならない
WINTER = 4;
}


reserved

数字とラベルを予約することができる。過去に別の意味で使ったことのある値を、念のため欠番にしておくために使える。

数字とラベルは1つの文でreservedにすることはできない。別々に書く必要がある。

enum Foo {

reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}