Protobuf Editionsの概要

Protobuf Editionsの機能の概要。

Protobuf Editionsは、Protocol Buffersで使用してきたproto2およびproto3の指定に代わるものです。proto定義ファイルの先頭にsyntax = "proto2"またはsyntax = "proto3"を追加する代わりに、edition = "2024"のようなエディション番号を使用して、ファイルが持つデフォルトの動作を指定します。Editionsを使用すると、言語を時間をかけて段階的に進化させることができます。

以前のバージョンが持っていたハードコードされた動作の代わりに、Editionsは、機能ごとにデフォルト値(動作)を持つ機能のコレクションを表します。機能は、protoc、コードジェネレーター、およびprotobufランタイムの動作を指定する、ファイル、メッセージ、フィールド、列挙型などのオプションです。選択したエディションのデフォルトの動作がニーズに合わない場合、これらの異なるレベル(ファイル、メッセージ、フィールドなど)で動作を明示的にオーバーライドできます。オーバーライドをさらにオーバーライドすることもできます。このトピックの後半にあるレキシカルスコープに関するセクションで、これについて詳しく説明します。

最新のリリースエディションは2023です。

このトピックの例ではエディション2024の機能を示していますが、エディション2024は現在**プレリリースレビュー中**であり、まだプロダクションコードには推奨されていません。

機能のライフサイクル

Editionsは、機能のライフサイクルに対する基本的な増分を提供します。機能には、導入、デフォルトの動作の変更、非推奨化、および削除という予想されるライフサイクルがあります。例:

  1. エディション2031では、feature.amazing_new_featureがデフォルト値falseで作成されます。この値は、以前のすべてのエディションと同じ動作を維持します。つまり、デフォルトでは影響はありません。すべての新機能がno-opオプションにデフォルト設定されるわけではありませんが、この例ではamazing_new_featureはno-opにデフォルト設定されます。

  2. 開発者は、.protoファイルをedition = "2031"に更新します。

  3. 後のエディション、たとえばエディション2033では、feature.amazing_new_featureのデフォルトがfalseからtrueに切り替わります。これがすべてのプロトにとって望ましい動作であり、protobufチームがこの機能を作成した理由です。

    Prototillerツールを使用して、以前のバージョンのprotoファイルをエディション2033に移行すると、以前の動作を保持し続けるために、必要に応じて明示的なfeature.amazing_new_feature = falseエントリが追加されます。開発者は、新しい動作を.protoファイルに適用したいときに、これらの新しく追加された設定を削除します。

  1. ある時点で、feature.amazing_new_featureはエディションで非推奨とマークされ、後のエディションで削除されます。

    機能が削除されると、その動作のコードジェネレーターとそれをサポートするランタイムライブラリも削除される可能性があります。ただし、タイムラインは十分に余裕を持って設定されます。ライフサイクルの前のステップの例に従うと、非推奨化はエディション2034で発生する可能性がありますが、約2年後のエディション2036まで削除されない可能性があります。機能の削除は、常にメジャーバージョンのバンプを開始します。

Googleの移行期間全体と非推奨期間をすべて使ってコードをアップグレードすることができます。

前述のライフサイクルの例では機能にブール値を使用しましたが、機能には列挙型を使用することもできます。たとえば、features.field_presenceにはLEGACY_REQUIREDEXPLICITIMPLICITという値があります。

Protobuf Editionsへの移行

Editionsは既存のバイナリを破損せず、メッセージのバイナリ、テキスト、またはJSONシリアル化形式を変更しません。エディション2023は、可能な限り最小限の破壊となるように設計されました。それはベースラインを確立し、proto2とproto3の定義を新しい単一の定義形式に統合しました。

より多くのエディションがリリースされるにつれて、機能のデフォルトの動作が変更される可能性があります。Prototillerに.protoファイルのno-op変換を実行させることも、新しい動作の一部またはすべてを受け入れることもできます。Editionsは年に1回程度のリリースが予定されています。

Proto2からEditionsへ

このセクションでは、proto2ファイルと、Prototillerツールを実行して定義ファイルをProtobuf Editions構文を使用するように変更した後の状態を示します。

Proto2構文

// proto2 file
syntax = "proto2";

package com.example;

message Player {
  // in proto2, optional fields have explicit presence
  optional string name = 1 [default = "N/A"];
  // proto2 still supports the problematic "required" field rule
  required int32 id = 2;
  // in proto2 this is not packed by default
  repeated int32 scores = 3;

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  // in proto2 enums are closed
  optional Handed handed = 4;

  reserved "gender";
}

Editions構文

// Edition version of proto2 file
edition = "2024";

package com.example;

option features.utf8_validation = NONE;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;

// Sets the default behavior for C++ strings
option features.(pb.cpp).string_type = STRING;

message Player {
  // fields have explicit presence, so no explicit setting needed
  string name = 1 [default = "N/A"];
  // to match the proto2 behavior, LEGACY_REQUIRED is set at the field level
  int32 id = 2 [features.field_presence = LEGACY_REQUIRED];
  // to match the proto2 behavior, EXPANDED is set at the field level
  repeated int32 scores = 3 [features.repeated_field_encoding = EXPANDED];

  export enum Handed {
    // this overrides the default editions behavior, which is OPEN
    option features.enum_type = CLOSED;
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

Proto3からEditionsへ

このセクションでは、proto3ファイルと、Prototillerツールを実行して定義ファイルをProtobuf Editions構文を使用するように変更した後の状態を示します。

Proto3構文

// proto3 file
syntax = "proto3";

package com.example;

message Player {
  // in proto3, optional fields have explicit presence
  optional string name = 1;
  // in proto3 no specified field rule defaults to implicit presence
  int32 id = 2;
  // in proto3 this is packed by default
  repeated int32 scores = 3;

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  // in proto3 enums are open
  optional Handed handed = 4;

  reserved "gender";
}

Editions構文

// Editions version of proto3 file
edition = "2024";

package com.example;

option features.utf8_validation = NONE;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;

// Sets the default behavior for C++ strings
option features.(pb.cpp).string_type = STRING;

message Player {
  // fields have explicit presence, so no explicit setting needed
  string name = 1 [default = "N/A"];
  // to match the proto3 behavior, IMPLICIT is set at the field level
  int32 id = 2 [features.field_presence = IMPLICIT];
  // PACKED is the default state, and is provided just for illustration
  repeated int32 scores = 3 [features.repeated_field_encoding = PACKED];

  export enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

レキシカルスコープ

Editions構文はレキシカルスコープをサポートしており、機能ごとに許可されるターゲットのリストがあります。たとえば、エディション2023では、機能はファイルレベルまたは最も細かい粒度のレベルでのみ指定できます。レキシカルスコープの実装により、ファイル全体で機能のデフォルトの動作を設定し、メッセージ、フィールド、列挙型、列挙値、oneof、サービス、またはメソッドレベルでその動作をオーバーライドできます。上位レベル(ファイル、メッセージ)での設定は、同じスコープ(フィールド、列挙値)内で設定が行われない場合に適用されます。明示的に設定されていない機能は、.protoファイルに使用されているエディションバージョンで定義された動作に準拠します。

次のコードサンプルは、ファイル、フィールド、および列挙型レベルで設定されているいくつかの機能を示しています。

edition = "2024";

option features.enum_type = CLOSED;

message Person {
  string name = 1;
  int32 id = 2 [features.field_presence = IMPLICIT];

  enum Pay_Type {
    PAY_TYPE_UNSPECIFIED = 1;
    PAY_TYPE_SALARY = 2;
    PAY_TYPE_HOURLY = 3;
  }

  enum Employment {
    option features.enum_type = OPEN;
    EMPLOYMENT_UNSPECIFIED = 0;
    EMPLOYMENT_FULLTIME = 1;
    EMPLOYMENT_PARTTIME = 2;
  }
  Employment employment = 4;
}

前の例では、プレゼンス機能はIMPLICITに設定されています。設定されていなければ、デフォルトでEXPLICITになります。Pay_Type enumはファイルレベルの設定を適用するため、CLOSEDになります。ただし、Employment enumは、enum内で設定されているため、OPENになります。

Prototiller

Prototillerツールがリリースされる際、エディションへの移行とエディション間の移行を容易にするための移行ガイドと移行ツールを提供します。このツールを使用すると、次のことが可能になります。

  • proto2およびproto3定義ファイルを新しいエディション構文に大規模に変換する
  • ファイルをあるエディションから別のエディションに移行する
  • その他の方法でprotoファイルを操作する

下位互換性

Protobuf Editionsは、可能な限り最小限の破壊となるように構築されています。たとえば、proto2およびproto3の定義をエディションベースの定義ファイルにインポートでき、その逆も可能です。

// file myproject/foo.proto
syntax = "proto2";

enum Employment {
  EMPLOYMENT_UNSPECIFIED = 0;
  EMPLOYMENT_FULLTIME = 1;
  EMPLOYMENT_PARTTIME = 2;
}
// file myproject/edition.proto
edition = "2024";

import "myproject/foo.proto";

proto2またはproto3からエディションに移行すると生成されたコードは変更されますが、ワイヤー形式は変更されません。エディション構文のproto定義を使用して、proto2およびproto3のデータファイルまたはファイルストリームに引き続きアクセスできます。

文法の変更

エディションでは、proto2およびproto3と比較して文法にいくつかの変更があります。

構文の説明

syntax要素の代わりに、edition要素を使用します

syntax = "proto2";
syntax = "proto3";
edition = "2028";

予約済み名

予約する際にフィールド名と列挙値名を引用符で囲む必要がなくなりました

reserved foo, bar;

グループ構文

proto2で利用可能だったグループ構文は、エディションでは削除されました。グループが使用していた特殊なワイヤー形式は、DELIMITEDメッセージエンコーディングを使用することで引き続き利用できます。

必須ラベル

proto2でのみ利用可能だったrequiredラベルは、エディションでは利用できません。基になる機能は、features.field_presence=LEGACY_REQUIREDを使用することで引き続き利用できます。

import option

エディション2024では、import option構文を使用したオプションのインポートがサポートされるようになりました。

オプションのインポートは、他のimportステートメントの後に記述する必要があります。

通常のimportステートメントとは異なり、オプションのインポートは、他のシンボルをインポートすることなく、.protoファイルで定義されたカスタムオプションのみをインポートします。

これは、メッセージと列挙型がオプションのインポートから除外されることを意味します。次の例では、Barメッセージはfoo.protoでフィールド型として使用できませんが、型Barのオプションは引き続き設定できます。

// bar.proto
edition = "2024";

import "google/protobuf/descriptor.proto";

message Bar {
  bool bar = 1;
}

extend proto2.FileOptions {
  bool file_opt1 = 5000;
  Bar file_opt2 = 5001;
}

// foo.proto:
edition = "2024";

import option "bar.proto";

option (file_opt1) = true;
option (file_opt2) = {bar: true};

message Foo {
  // Bar bar = 1; // This is not allowed
}

オプションのインポートは、そのシンボルに対して生成されたコードを必要としないため、depsの代わりにproto_libraryoption_depsとして提供する必要があります。これにより、到達不能なコードの生成が回避されます。

proto_library(
  name = "foo",
  srcs = ["foo.proto"],
  option_deps = [":custom_option_proto"]
)

不要なコードの生成を避けるために、protobuf言語機能やその他のカスタムオプションをインポートする際には、オプションのインポートとoption_depsを強く推奨します。

これは、エディション2024で削除されたimport weakに代わるものです。

export / localキーワード

exportおよびlocalキーワードは、インポート可能なシンボルのシンボル可視性の修飾子として、エディション2024で追加されました。これは、features.default_symbol_visibilityで指定されたデフォルトの動作から派生しています。

これは、他のprotoファイルからインポートできるシンボルを制御しますが、コード生成には影響しません。

エディション2024では、これらはデフォルトでmessageおよびenumシンボルすべてに設定できます。ただし、default_symbol_visibility機能の一部の値は、エクスポート可能なシンボルをさらに制限します。

// Top-level symbols are exported by default in Edition 2024
message LocalMessage {
  int32 baz = 1;
  // Nested symbols are local by default in Edition 2024; applying the `export`
  // keyword overrides this
  export enum ExportedNestedEnum {
    UNKNOWN_EXPORTED_NESTED_ENUM_VALUE = 0;
  }
}

// The `local` keyword overrides the default behavior of exporting messages
local message AnotherMessage {
  int32 foo = 1;
}

import weakとWeak Field Option

エディション2024以降、weak importは許可されなくなりました。

以前にC++とGoの生成されたコードなしでカスタムオプションをインポートするために「弱い依存関係」を宣言する目的でimport weakに依存していた場合は、代わりにimport optionを使用するように移行する必要があります。

詳細については、import optionを参照してください。

ctypeフィールドオプション

エディション2024以降、ctypeフィールドオプションは許可されなくなりました。代わりにstring_type機能を使用してください。

詳細については、features.(pb.cpp).string_typeを参照してください。

java_multiple_filesファイルオプション

エディション2024以降、java_multiple_filesファイルオプションは利用できなくなりました。代わりに、features.(pb.java).nest_in_file_class Java機能を使用してください。