Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

protocol buffers ENUM型 完全ガイド

More than 1 year has passed since last update.

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";
}
Hiraku
PHP, Go界隈をうろうろしています。最近はgRPCと戦ってる。 特に明示していなければ、記事中のソースコード片は `CC-0 1.0` とします。出典表示無しで自由にコピペして頂いて構いません。 ただ、記事自体をコピペされるのは嫌なので、ソースコード部分以外の文章は通常通り全ての著作権を私が保持するものとします。 引用を超える範囲のコピペは止めて下さい。
http://blog.tojiru.net/
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away