Rust生成コードガイド
このページでは、プロトコルバッファコンパイラが指定されたプロトコル定義に対して生成するRustコードについて正確に説明します。
このドキュメントでは、プロトコルバッファコンパイラがproto2、proto3、およびprotobufエディション用に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カーネル
- 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の新しいインスタンスを作成します。
トレイト
gencodeのサイズ、名前の衝突の問題、gencodeの安定性など、多くの理由から、メッセージの最も一般的な機能は、固有の実装ではなく、トレイトとして実装されています。
ほとんどのユーザーは、トレイトとproto!マクロのみを含み、他の型を含まないプレリュードをインポートする必要があります (use protobuf::prelude::*)。プレリュードを避けたい場合は、必要に応じて特定のトレイトをインポートできます (直接インポートしたい場合は、
ここにドキュメントでトレイトの名前と定義を参照してください)。
fn parse(data: &[u8]) -> Result<Self, ParseError>: メッセージの新しいインスタンスを解析します。fn parse_dont_enforce_required(data: &[u8]) -> Result<Self, ParseError>:parseと同じですが、proto2のrequiredフィールドが欠落していても失敗しません。fn clear(&mut self): メッセージをクリアします。fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>: 既存のインスタンスをクリアして解析します。fn clear_and_parse_dont_enforce_required(&mut self, data: &[u8]) -> Result<(), ParseError>:parseと同じですが、proto2のrequiredフィールドが欠落していても失敗しません。fn serialize(&self) -> Result<Vec<u8>, SerializeError>: メッセージをProtobufワイヤーフォーマットにシリアル化します。シリアル化は失敗することがありますが、まれです。失敗の理由としては、表現が最大エンコードメッセージサイズ(2GiB未満である必要があります)を超える場合や、未設定のrequiredフィールド(proto2)などがあります。fn take_from(&mut self, other):otherをselfに移動し、selfが以前に持っていた状態を破棄します。fn copy_from(&mut self, other):otherをselfにコピーし、selfが以前に持っていた状態を破棄します。otherは変更されません。fn merge_from(&mut self, other):otherをselfにマージします。fn as_view(&self) -> FooView<'_>:Fooへのイミュータブルなハンドル(ビュー)を返します。これはプロキシ型に関するセクションでさらに説明されています。fn as_mut(&mut self) -> FooMut<'_>:Fooへのミュータブルなハンドル(ミュータブル)を返します。これはプロキシ型に関するセクションでさらに説明されています。
Fooはさらに以下の標準トレイトを実装しています
std::fmt::Debugstd::default::Defaultstd::clone::Clonestd::marker::Sendstd::marker::Sync
新しいインスタンスを流暢に作成する
セッターのAPI設計は確立されたProtobufイディオムに従っていますが、新しいインスタンスを構築する際の冗長性は、特定の他の言語では軽微な問題点です。これを軽減するために、より簡潔に/流暢に新しいインスタンスを作成するために使用できるproto!マクロを提供しています。
たとえば、次のように書く代わりに、
let mut msg = SomeMsg::new();
msg.set_x(1);
msg.set_y("hello");
msg.some_submessage_mut().set_z(42);
このマクロを使って以下のように書くことができます。
let msg = proto!(SomeMsg {
x: 1,
y: "hello",
some_submsg: SomeSubmsg {
z: 42
}
});
メッセージプロキシ型
いくつかの技術的な理由により、特定のケースでネイティブの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というモジュールが作成されます。同様に、深くネストされた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では、optionalフィールドが明示的なプレゼンスを持ちます。proto3では、メッセージフィールドとoneofまたはoptionalフィールドのみが明示的なプレゼンスを持ちます。エディションでは、features.field_presenceオプションを使用してプレゼンスを設定します。
数値フィールド
このフィールド定義について:
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型に置き換えられます。
文字列とバイトフィールド
これらのフィールド定義について
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)バリアントを持つOptionalを返し、設定されていない場合はUnset(default value)バリアントを持つOptionalを返します。fn set_foo(&mut self, val: impl IntoProxied<ProtoString>): フィールドの値を設定します。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<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()はデフォルト値を返します。
埋め込みメッセージフィールド
任意のプロト構文バージョンからのメッセージ型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<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を返します。
暗黙的なプレゼンスを持つフィールド (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<ProtoString>): フィールドをvalueに設定します。
bytes型のフィールドの場合、コンパイラは代わりにProtoBytes型を生成します。
Cordサポート付きの単数形文字列およびバイトフィールド
[ctype = CORD]は、バイトと文字列をC++ Protobufsで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
これらのフィールド定義のいずれかの場合
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型を生成します。
コンパイラは以下のアクセッサメソッドを生成します
fn foo(&self) -> &ProtoStr: フィールドの現在の値を返します。フィールドが設定されていない場合は、空の文字列/空のバイトを返します。fn set_foo(&mut self, value: impl IntoProxied<ProtoString>): フィールドを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つのアクセッサメソッドを生成します。
エディションでは、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>: 基になる繰り返しフィールドのビューを返します。fn foo_mut(&mut self) -> RepeatedMut<'_, i32>: 基になる繰り返しフィールドへのミュータブルなハンドルを返します。fn set_foo(&mut self, src: impl IntoProxied<Repeated<i32>>): 基になる繰り返しフィールドをsrcで提供された新しい繰り返しフィールドに設定します。
異なるフィールド型の場合、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
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<_>: どのフィールドが設定されているか、およびフィールドの値を表す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名でプレフィックスが付けられます(これは、値がそれらを含むenumによってスコープされないC++ enumのセマンティクスに従います)。生成されたRust定数は`impl`内でスコープされるため、.protoファイルに追加すると有益な追加プレフィックスはRustでは冗長になります。
拡張機能 (proto2のみ)
拡張機能のためのRust APIは現在開発中です。拡張フィールドは解析/シリアル化を通じて維持され、C++相互運用の場合、メッセージがRustからアクセスされた場合(メッセージのコピーまたはマージの場合には伝播されます)、設定された拡張機能は保持されます。
アリーナ割り当て
アリーナ割り当てされたメッセージのRust APIはまだ実装されていません。
内部的に、upbカーネル上のProtobuf Rustはアリーナを使用しますが、C++カーネル上では使用しません。ただし、C++でアリーナ割り当てされたメッセージへの参照(定数と可変の両方)は、安全にRustに渡してアクセスまたは変更できます。
サービス
サービスのためのRust APIはまだ実装されていません。