Rust 生成コードガイド

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

このページでは、プロトコルバッファーコンパイラが与えられたプロトコル定義に対してどのようなRustコードを生成するかを正確に説明します。

このドキュメントでは、プロトコルバッファコンパイラがproto2、proto3、およびProtobuf Editions向けにRustコードを生成する方法について説明します。proto2、proto3、およびEditionsで生成されるコードの違いは強調されています。このドキュメントを読む前に、proto2言語ガイドproto3言語ガイド、またはEditionsガイドを読む必要があります。

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 コード。

各`proto_library`には`generated.rs`ファイルも含まれており、これはクレートのエントリーポイントとして扱われます。このファイルは、クレート内の他のすべてのRustファイルからのシンボルを再エクスポートします。

パッケージ

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

メッセージ

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

message Foo {}

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

  • fn new() -> Self: Foo の新しいインスタンスを作成します。
  • fn parse(data: &[u8]) -> Result: dataFoo の有効なワイヤーフォーマット表現を保持している場合、dataFoo のインスタンスにパースします。そうでない場合、関数はエラーを返します。
  • fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>: .clear()parse() を連続して呼び出すのと同様。
  • fn serialize(&self) -> Result, SerializeError>: メッセージを Protobuf ワイヤー形式にシリアライズします。シリアライズは失敗する可能性がありますが、まれです。失敗の理由には、最大メッセージサイズ超過、メモリ不足、未設定の必須フィールド(proto2)などが挙げられます。
  • fn merge_from(&mut self, other): selfother をマージします。
  • 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` という型を使用してこれらの概念を表現する必要があります。これらの状況は、共有された可変参照です。

  • メッセージ
  • 繰り返しフィールド
  • マップフィールド

例えば、コンパイラは `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`という名前のモジュールが作成されます。同様に、深くネストされた列挙型`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では、`optional`フィールドは明示的なプレゼンスを持ちます。proto3では、メッセージフィールドと`oneof`または`optional`フィールドのみが明示的なプレゼンスを持ちます。Editionsでは、`features.field_presence`オプションを使用してプレゼンスを設定します。

数値フィールド

このフィールド定義について:

int32 foo = 1;

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

  • fn has_foo(&self) -> bool: フィールドが設定されている場合は `true` を返します。
  • fn foo(&self) -> i32: フィールドの現在の値を返します。フィールドが設定されていない場合は、デフォルト値を返します。
  • fn foo_opt(&self) -> protobuf::Optional: フィールドが設定されている場合は`Set(value)`、設定されていない場合は`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 型に置き換えられます。

文字列とバイトフィールド

これらのフィールド定義について

string foo = 1;
bytes foo = 1;

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

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

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

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 has_foo(&self) -> bool: フィールドが設定されている場合は `true` を返します。
  • fn foo(&self) -> Bar: フィールドの現在の値を返します。フィールドが設定されていない場合、デフォルト値を返します。
  • fn foo_opt(&self) -> Optional: フィールドが設定されている場合は`Set(value)`、設定されていない場合は`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()`はデフォルト値を返します。

埋め込みメッセージフィールド

任意のプロト構文バージョンからのメッセージ型`Bar`が与えられた場合

message Bar {}

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


message MyMessage {
  Bar foo = 1;
}

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

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

暗黙的な存在を持つフィールド(proto3 および Editions)

暗黙的なプレゼンスとは、フィールドがデフォルト値と値が設定されていない状態を区別しないことを意味します。proto3では、フィールドはデフォルトで暗黙的なプレゼンスを持ちます。エディションでは、`field_presence`機能を`IMPLICIT`に設定することで、暗黙的なプレゼンスを持つフィールドを宣言できます。

数値フィールド

これらのフィールド定義について

// proto3
int32 foo = 1;

// editions
message MyMessage {
  int32 foo = 1 [features.field_presence = IMPLICIT];
}

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

  • fn foo(&self) -> i32: フィールドの現在の値を返します。フィールドが設定されていない場合、0を返します。
  • fn set_foo(&mut self, val: i32): フィールドの値を設定します。

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

文字列とバイトフィールド

これらのフィールド定義について

// proto3
string foo = 1;
bytes foo = 1;

// editions
string foo = 1 [features.field_presence = IMPLICIT];
bytes bar = 2 [features.field_presence = IMPLICIT];

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

  • fn foo(&self) -> &ProtoStr: フィールドの現在の値を返します。フィールドが設定されていない場合、空文字列/空バイトを返します。
  • fn set_foo(&mut self, value: IntoProxied): フィールドを`value`に設定します。

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

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

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

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

一般的な場合、短い文字列では`absl::Cord`はそのデータを連続した文字列として格納します。この場合、cordアクセサは`ProtoStringCow::Borrowed`を返します。基になる`absl::Cord`が非連続の場合、アクセサはデータをcordから所有する`ProtoString`にコピーし、`ProtoStringCow::Owned`を返します。`ProtoStringCow`は`Deref`を実装しています。

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

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): フィールドを`value`に設定します。この関数を呼び出すと、`foo()`は`value`を返し、`has_foo()`は`true`を返します。
  • fn has_foo(&self) -> bool: フィールドが設定されている場合は `true` を返します。
  • fn clear_foo(&mut self): フィールドの値をクリアします。これを呼び出すと、`has_foo()`は`false`を返し、`foo()`はデフォルト値を返します。Cordはまだ実装されていません。

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

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

  • fn foo(&self) -> &ProtoStr: フィールドの現在の値を返します。フィールドが設定されていない場合、空文字列/空バイトを返します。
  • fn set_foo(&mut self, value: impl IntoProxied): フィールドを`value`に設定します。

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);
}

これらのフィールド定義について

// proto3
Bar foo = 1;

// editions
message MyMessage {
 Bar foo = 1 [features.field_presence = IMPLICIT];
}

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

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

繰り返しフィールド

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

Editionsでは、`repeated_field_encoding`機能を使用して、繰り返しプリミティブフィールドのワイヤーフォーマットエンコーディングを制御できます。

// proto2
repeated int32 foo = 1; // EXPANDED by default

// proto3
repeated int32 foo = 1; // PACKED by default

// editions
repeated int32 foo = 1 [features.repeated_field_encoding = PACKED];
repeated int32 bar = 2 [features.repeated_field_encoding = EXPANDED];

上記のいずれかのフィールド定義が与えられた場合、コンパイラは以下のアクセサメソッドを生成します。

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

異なるフィールド型の場合、`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>): 基になるマップを`src`に設定します。

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

Any

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

message Any {
  string type_url = 1;
  bytes value = 2;
}

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<_>: どのフィールドが設定されているかを示す列挙型のバリアントとフィールドの値を返します。フィールドが設定されていない場合は`not_set`を返します。
  • fn example_name_case(&self) -> ExampleNameCase: どのフィールドが設定されているかを示す列挙型のバリアントを返します。フィールドが設定されていない場合は`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);
  }

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

拡張機能(proto2 のみ)

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

アリーナアロケーション

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

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

サービス

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