Rust 生成コードガイド

プロトコルバッファコンパイラが与えられたプロトコル定義に対して生成するメッセージオブジェクトのAPIについて説明します。

このページでは、プロトコルバッファコンパイラが任意のプロトコル定義に対して生成するRustコードを正確に説明します。

proto2 と proto3 の生成コードの違いが強調されています。このドキュメントを読む前に、proto2 言語ガイド および/または proto3 言語ガイド をお読みください。

Protobuf Rust

Protobuf Rust は、'カーネル' と呼ぶ他の既存のプロトコルバッファ実装の上に構築できるように設計されたプロトコルバッファの実装です。

複数の非Rustカーネルをサポートするという決定は、Rustの標準型である`str`の代わりに`ProtoStr`のようなカスタム型を使用する選択を含め、当社の公開APIに大きな影響を与えました。このトピックの詳細については、Rust Proto 設計上の決定 を参照してください。

生成されるファイル名

各 `rust_proto_library` は1つのクレートとしてコンパイルされます。最も重要な点として、対応する `proto_library` の `srcs` にあるすべての `.proto` ファイルに対して1つのRustファイルが出力され、これらのすべてのファイルが1つのクレートを形成します。

コンパイラによって生成されるファイルはカーネルによって異なります。一般的に、出力ファイルの名前は `.proto` ファイルの名前を取得し、拡張子を置き換えることで計算されます。

生成されるファイル

  • C++ カーネル
    • `.c.pb.rs` - 生成されたRustコード
    • `.pb.thunks.cc` - 生成されたC++サンク (Rustコードが呼び出し、C++ Protobuf APIに委任するグルーコード)。
  • C++ Lite カーネル
    • <C++ カーネルと同じ>
  • UPB カーネル
    • `.u.pb.rs` - 生成されたRustコード。
      (ただし、`rust_proto_library` は `upb_proto_aspect` によって生成される `.thunks.c` ファイルに依存します。)

`proto_library` が複数のファイルを含む場合、最初のファイルは「プライマリ」ファイルとして宣言され、クレートのエントリポイントとして扱われます。そのファイルには、`.proto` ファイルに対応する生成コードと、すべての「セカンダリ」ファイルに対応するファイルで定義されているすべてのシンボルの再エクスポートが含まれます。

パッケージ

他のほとんどの言語とは異なり、Rustコード生成では `.proto` ファイル内の `package` 宣言は使用されません。代わりに、各 `rust_proto_library(name = "some_rust_proto")` ターゲットは、そのターゲット内のすべての `.proto` ファイルの生成コードを含む `some_rust_proto` という名前のクレートを出力します。

メッセージ

メッセージ宣言が与えられた場合

message Foo {}

コンパイラは `Foo` という名前の構造体を生成します。`Foo` 構造体は以下のメソッドを定義します

  • fn new() -> Self: `Foo` の新しいインスタンスを作成します。
  • fn parse(data: &[u8]) -> Result<Self, protobuf::ParseError>: `data` が `Foo` の有効なワイヤー形式表現を保持している場合、`data` を `Foo` のインスタンスにパースします。それ以外の場合、関数はエラーを返します。
  • fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>: `.clear()` と `parse()` を連続して呼び出すのと同様です。
  • fn serialize(&self) -> Result<Vec<u8>, SerializeError>: メッセージをProtobufワイヤー形式にシリアライズします。シリアライズは失敗することがありますが、まれです。失敗の理由には、最大メッセージサイズ超過、メモリ不足、未設定の必須フィールド (proto2) などがあります。
  • fn merge_from(&mut self, other): `self` を `other` とマージします。
  • fn as_view(&self) -> FooView<'_>: `Foo` への不変ハンドル (ビュー) を返します。これはプロキシ型のセクションでさらに詳しく説明されています。
  • fn as_mut(&mut self) -> FooMut<'_>: `Foo` への可変ハンドル (mut) を返します。これはプロキシ型のセクションでさらに詳しく説明されています。

`Foo` は以下のトレイトを実装しています

  • std::fmt::Debug
  • std::default::Default
  • std::clone::Clone
  • std::ops::Drop
  • std::marker::Send
  • std::marker::Sync

メッセージプロキシ型

単一のRust APIで複数のカーネルをサポートする必要がある結果として、一部の状況ではネイティブのRust参照 (`&T` および `&mut T`) を使用できず、代わりに型である `View` と `Mut` を使用してこれらの概念を表現する必要があります。これらの状況は、共有および可変参照のものです。

  • メッセージ
  • Repeated フィールド
  • Map フィールド

例えば、コンパイラは `Foo` と共に構造体 `FooView<'a>` および `FooMut<'msg>` を出力します。これらの型は `&Foo` および `&mut Foo` の代わりに使用され、借用チェッカーの動作に関してネイティブのRust参照と同じように振る舞います。ネイティブの借用と同様に、Viewは `Copy` であり、借用チェッカーは任意の数のView、または一度に最大1つのMutが存在することを強制します。

このドキュメントの目的のために、私たちは所有するメッセージ型 (`Foo`) のために出力されるすべてのメソッドを説明することに焦点を当てます。`&self` レシーバーを持つこれらの関数の一部は `FooView<'msg>` にも含まれます。`&self` または `&mut self` のいずれかを持つこれらの関数の一部は `FooMut<'msg>` にも含まれます。

View / Mut 型から所有するメッセージ型を作成するには、`to_owned()` を呼び出します。これはディープコピーを作成します。

ネストされた型

メッセージ宣言が与えられた場合

message Foo {
  message Bar {
      enum Baz { ... }
  }
}

`Foo` という名前の構造体に加えて、`Bar` の構造体を含む `foo` という名前のモジュールが作成されます。同様に、深くネストされたenum `Baz` を含む `bar` という名前のネストされたモジュールも作成されます。

pub struct Foo {}

pub mod foo {
   pub struct Bar {}
   pub mod bar {
      pub struct Baz { ... }
   }
}

フィールド

前のセクションで説明したメソッドに加えて、プロトコルバッファコンパイラは、`.proto` ファイル内のメッセージ内で定義された各フィールドに対して一連のアクセサメソッドを生成します。

Rustのスタイルに従い、メソッドは小文字/スネークケースで記述されます。例えば、`has_foo()` や `clear_foo()` などです。アクセサのフィールド名部分の大文字/小文字は元の.protoファイルのスタイルを維持していることに注意してください。これは、.protoファイルスタイルガイド に従って小文字/スネークケースであるべきです。

オプションの数値フィールド (proto2 および proto3)

これらのフィールド定義のいずれかに対して

optional int32 foo = 1;
required int32 foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn has_foo(&self) -> bool: フィールドが設定されている場合 `true` を返します。
  • fn foo(&self) -> i32: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • fn foo_opt(&self) -> protobuf::Optional<i32>: フィールドが設定されている場合はバリアント `Set(value)` を持つ optional を返し、設定されていない場合は `Unset(default value)` を持つ optional を返します。
  • fn set_foo(&mut self, val: i32): フィールドの値を設定します。これを呼び出した後、`has_foo()` は `true` を返し、`foo()` は `value` を返します。
  • fn clear_foo(&mut self): フィールドの値をクリアします。これを呼び出した後、`has_foo()` は `false` を返し、`foo()` はデフォルト値を返します。

他の数値フィールド型 (`bool` を含む) の場合、`int32` は スカラー値型テーブル に従って対応するRust型に置き換えられます。

暗黙的な存在の数値フィールド (proto3)

これらのフィールド定義の場合

int32 foo = 1;
  • fn foo(&self) -> i32: フィールドの現在の値を返します。フィールドが設定されていない場合、`0` を返します。
  • fn set_foo(&mut self, val: i32): フィールドの値を設定します。これを呼び出した後、`foo()` は値を返します。

他の数値フィールド型 (`bool` を含む) の場合、`int32` は スカラー値型テーブル に従って対応するRust型に置き換えられます。

オプションの文字列/バイトフィールド (proto2 および proto3)

これらのフィールド定義のいずれかの場合

optional string foo = 1;
required string foo = 1;
optional bytes foo = 1;
required bytes foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn has_foo(&self) -> bool: フィールドが設定されている場合 `true` を返します。
  • fn foo(&self) -> &protobuf::ProtoStr: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • fn foo_opt(&self) -> protobuf::Optional<&ProtoStr>: フィールドが設定されている場合はバリアント `Set(value)` を持つ optional を返し、設定されていない場合は `Unset(default value)` を持つ optional を返します。
  • fn clear_foo(&mut self): フィールドの値をクリアします。これを呼び出した後、`has_foo()` は `false` を返し、`foo()` はデフォルト値を返します。

`bytes` 型のフィールドの場合、コンパイラは代わりに `ProtoBytes` 型を生成します。

暗黙的な存在の文字列/バイトフィールド (proto3)

これらのフィールド定義の場合

optional string foo = 1;
string foo = 1;
optional bytes foo = 1;
bytes foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn foo(&self) -> &ProtoStr: フィールドの現在の値を返します。フィールドが設定されていない場合、空の文字列/空のバイトを返します。
  • fn foo_opt(&self) -> Optional<&ProtoStr>: フィールドが設定されている場合はバリアント `Set(value)` を持つ optional を返し、設定されていない場合は `Unset(default value)` を持つ optional を返します。
  • fn set_foo(&mut self, value: IntoProxied<ProtoString>): フィールドを `value` に設定します。この関数を呼び出した後、`foo()` は `value` を返し、`has_foo()` は `true` を返します。
  • fn has_foo(&self) -> bool: フィールドが設定されている場合 `true` を返します。
  • fn clear_foo(&mut self): フィールドの値をクリアします。これを呼び出した後、`has_foo()` は `false` を返し、`foo()` はデフォルト値を返します。

`bytes` 型のフィールドの場合、コンパイラは代わりに `ProtoBytes` 型を生成します。

Cord サポート付きの単一文字列およびバイトフィールド

[ctype = CORD] を使用すると、C++ Protobuf でバイトと文字列を absl::Cord として保存できます。`absl::Cord` には現在Rustに同等の型がありません。Protobuf Rust は enum を使用してコードフィールドを表します。

enum ProtoStringCow<'a> {
  Owned(ProtoString),
  Borrowed(&'a ProtoStr)
}

一般的なケースでは、小さな文字列の場合、`absl::Cord` はデータを連続した文字列として保存します。この場合、コードアクセサは `ProtoStringCow::Borrowed` を返します。基になる `absl::Cord` が非連続の場合、アクセサはコードからデータを所有する `ProtoString` にコピーし、`ProtoStringCow::Owned` を返します。`ProtoStringCow` は `Deref<Target=ProtoStr>` を実装しています。

これらのフィールド定義のいずれかの場合

optional string foo = 1 [ctype = CORD];
string foo = 1 [ctype = CORD];
optional bytes foo = 1 [ctype = CORD];
bytes foo = 1 [ctype = CORD];

コンパイラは以下のアクセサメソッドを生成します

  • fn my_field(&self) -> ProtoStringCow<'_>: フィールドの現在の値を返します。フィールドが設定されていない場合、空の文字列/空のバイトを返します。
  • fn set_my_field(&mut self, value: IntoProxied<ProtoString>): フィールドを `value` に設定します。この関数を呼び出した後、`foo()` は `value` を返し、`has_foo()` は `true` を返します。
  • fn has_foo(&self) -> bool: フィールドが設定されている場合 `true` を返します。
  • fn clear_foo(&mut self): フィールドの値をクリアします。これを呼び出した後、`has_foo()` は `false` を返し、`foo()` はデフォルト値を返します。Cord はまだ実装されていません。

`bytes` 型のフィールドの場合、コンパイラは代わりに `ProtoBytesCow` 型を生成します。

オプションのEnumフィールド (proto2 および proto3)

enum型が与えられた場合

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

コンパイラは、各バリアントが関連定数である構造体を生成します

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Bar(i32);

impl Bar {
  pub const Unspecified: Bar = Bar(0);
  pub const Value: Bar = Bar(1);
  pub const OtherValue: Bar = Bar(2);
}

これらのフィールド定義のいずれかに対して

optional Bar foo = 1;
required Bar foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn has_foo(&self) -> bool: フィールドが設定されている場合 `true` を返します。
  • fn foo(&self) -> Bar: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • fn foo_opt(&self) -> Optional<Bar>: フィールドが設定されている場合はバリアント `Set(value)` を持つ optional を返し、設定されていない場合は `Unset(default value)` を持つ optional を返します。
  • fn set_foo(&mut self, val: Bar): フィールドの値を設定します。これを呼び出した後、`has_foo()` は `true` を返し、`foo()` は `value` を返します。
  • fn clear_foo(&mut self): フィールドの値をクリアします。これを呼び出した後、`has_foo()` は `false` を返し、`foo()` はデフォルト値を返します。

暗黙的な存在のEnumフィールド (proto3)

enum型が与えられた場合

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

コンパイラは、各バリアントが関連定数である構造体を生成します

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Bar(i32);

impl Bar {
  pub const Unspecified: Bar = Bar(0);
  pub const Value: Bar = Bar(1);
  pub const OtherValue: Bar = Bar(2);
}

これらのフィールド定義の場合

Bar foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn foo(&self) -> Bar: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • fn set_foo(&mut self, value: Bar): フィールドの値を設定します。これを呼び出した後、`has_foo()` は `true` を返し、`foo()` は `value` を返します。

オプションの埋め込みメッセージフィールド (proto2 および proto3)

メッセージ型が与えられた場合

message Bar {}

これらのフィールド定義のいずれかの場合

//proto2
optional Bar foo = 1;

//proto3
Bar foo = 1;
optional Bar foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn foo(&self) -> BarView<'_>: フィールドの現在の値のビューを返します。フィールドが設定されていない場合、空のメッセージを返します。
  • fn foo_mut(&mut self) -> BarMut<'_>: フィールドの現在の値への可変ハンドルを返します。フィールドが設定されていない場合、フィールドを設定します。このメソッドを呼び出した後、`has_foo()` は `true` を返します。
  • fn foo_opt(&self) -> protobuf::Optional<BarView>: フィールドが設定されている場合、`value` を持つバリアント `Set` を返します。それ以外の場合、デフォルト値を持つバリアント `Unset` を返します。
  • fn set_foo(&mut self, value: impl protobuf::IntoProxied<Bar>): フィールドを `value` に設定します。このメソッドを呼び出した後、`has_foo()` は `true` を返します。
  • fn has_foo(&self) -> bool: フィールドが設定されている場合 `true` を返します。
  • fn clear_foo(&mut self): フィールドをクリアします。このメソッドを呼び出した後、`has_foo()` は `false` を返します。

Repeated フィールド

任意のrepeatedフィールド定義に対して、コンパイラはフィールド型のみが異なる同じ3つのアクセサメソッドを生成します。

例えば、以下のフィールド定義が与えられた場合

repeated int32 foo = 1;

コンパイラは以下のアクセサメソッドを生成します

  • fn foo(&self) -> RepeatedView<'_, i32>: 基になるrepeatedフィールドのビューを返します。
  • fn foo_mut(&mut self) -> RepeatedMut<'_, i32>: 基になるrepeatedフィールドへの可変ハンドルを返します。
  • fn set_foo(&mut self, src: impl IntoProxied<Repeated<i32>>): 基になるrepeatedフィールドを、`src` で提供される新しいrepeatedフィールドに設定します。

異なるフィールド型の場合、`RepeatedView`、`RepeatedMut`、`Repeated` 型のそれぞれのジェネリック型のみが変更されます。例えば、`string` 型のフィールドが与えられた場合、`foo()` アクセサは `RepeatedView<'_, ProtoString>` を返します。

Map フィールド

このマップフィールド定義の場合

map<int32, int32> weight = 1;

コンパイラは以下の3つのアクセサメソッドを生成します

  • fn weight(&self) -> protobuf::MapView<'_, i32, i32>: 基になるマップの不変ビューを返します。
  • fn weight_mut(&mut self) -> protobuf::MapMut<'_, i32, i32>: 基になるマップへの可変ハンドルを返します。
  • fn set_weight(&mut self, src: protobuf::IntoProxied<Map<i32, i32>>): 基になるマップを `src` に設定します。

異なるフィールド型の場合、`MapView`、`MapMut`、`Map` 型のそれぞれのジェネリック型のみが変更されます。例えば、`string` 型のフィールドが与えられた場合、`foo()` アクセサは `MapView<'_, int32, ProtoString>` を返します。

Any

現時点では、Rust Protobuf では Any は特別扱いされません。この定義を持つ単純なメッセージであるかのように振る舞います。

message Any {
  string type_url = 1;
  bytes value = 2  [ctype = CORD];
}

Oneof

このような oneof 定義が与えられた場合

oneof example_name {
    int32 foo_int = 4;
    string foo_string = 9;
    ...
}

コンパイラは、oneof の外部で同じフィールドが `optional` フィールドとして宣言されたかのように、すべてのフィールドに対してアクセサ (ゲッター、セッター、ハッザー) を生成します。したがって、oneof フィールドを通常のフィールドのように操作できますが、1つを設定すると oneof ブロック内の他のフィールドがクリアされます。さらに、`oneof` ブロックに対して以下の型が出力されます

  #[non_exhaustive]
  #[derive(Debug, Clone, Copy)]

  pub enum ExampleNameOneof<'msg> {
    FooInt(i32) = 4,
    FooString(&'msg protobuf::ProtoStr) = 9,
    not_set(std::marker::PhantomData<&'msg ()>) = 0
  }
  #[derive(Debug, Copy, Clone, PartialEq, Eq)]

  pub enum ExampleNameCase {
    FooInt = 4,
    FooString = 9,
    not_set = 0
  }

さらに、以下の2つのアクセサを生成します

  • fn example_name(&self) -> ExampleNameOneof<_>: どのフィールドが設定されているかを示すenumバリアントとフィールドの値を返します。どのフィールドも設定されていない場合は `not_set` を返します。
  • fn example_name_case(&self) -> ExampleNameCase: どのフィールドが設定されているかを示すenumバリアントを返します。どのフィールドも設定されていない場合は `not_set` を返します。

列挙型

このような enum 定義が与えられた場合

enum FooBar {
  FOO_BAR_UNKNOWN = 0;
  FOO_BAR_A = 1;
  FOO_B = 5;
  VALUE_C = 1234;
}

コンパイラは以下を生成します

  #[derive(Clone, Copy, PartialEq, Eq, Hash)]
  #[repr(transparent)]
  pub struct FooBar(i32);

  impl FooBar {
    pub const Unknown: FooBar = FooBar(0);
    pub const A: FooBar = FooBar(1);
    pub const FooB: FooBar = FooBar(5);
    pub const ValueC: FooBar = FooBar(1234);
  }

enumと一致するプレフィックスを持つ値の場合、そのプレフィックスは削除されることに注意してください。これは人間工学を向上させるためです。Enum値は、兄弟enum間の名前の衝突を避けるために、enum名でプレフィックスが付けられるのが一般的です(C++ enumのセマンティクスに従い、値はそれを含むenumによってスコープ化されません)。生成されたRustの定数は `impl` 内でスコープ化されるため、.protoファイルで追加することが有益な追加プレフィックスは、Rustでは冗長になります。

拡張 (proto2 のみ)

拡張機能用のRust APIは現在開発中です。拡張フィールドはパース/シリアライズを通じて維持され、C++との相互運用の場合、メッセージがRustからアクセスされた場合(およびメッセージのコピーやマージの場合に伝播された場合)、設定された拡張は保持されます。

アリーナアロケーション

アリーナ割り当てされたメッセージ用のRust APIはまだ実装されていません。

内部的には、upbカーネル上のProtobuf Rustはアリーナを使用しますが、C++カーネル上では使用しません。しかし、C++でアリーナ割り当てされたメッセージへの参照(constとmutableの両方)は、安全にRustに渡してアクセスまたは変更することができます。

サービス

サービス用のRust APIはまだ実装されていません。