Rust 程式碼產生指南

說明通訊協定緩衝區編譯器針對任何給定的通訊協定定義產生的訊息物件 API。

本頁確切說明通訊協定緩衝區編譯器針對任何給定的通訊協定定義產生哪些 Rust 程式碼。

proto2 和 proto3 產生程式碼之間的任何差異都會加以強調。在閱讀本文檔之前,您應該先閱讀 proto2 語言指南 和/或 proto3 語言指南

Protobuf Rust

Protobuf Rust 是通訊協定緩衝區的實作,旨在能夠置於我們稱為「核心」的其他現有通訊協定緩衝區實作之上。

支援多個非 Rust 核心的決策已顯著影響我們的公開 API,包括選擇使用自訂類型 (如 ProtoStr) 而非 Rust std 類型 (如 str)。如需關於此主題的更多資訊,請參閱 Rust Proto 設計決策

產生的檔案名稱

每個 rust_proto_library 都將編譯為一個 crate。最重要的是,對於對應 proto_librarysrcs 中的每個 .proto 檔案,都會發出一個 Rust 檔案,並且所有這些檔案都會形成單個 crate。

編譯器產生的檔案會因核心而異。一般而言,輸出檔案的名稱是透過取得 .proto 檔案的名稱並替換副檔名來計算。

產生的檔案

  • C++ 核心
    • .c.pb.rs - 產生的 Rust 程式碼
    • .pb.thunks.cc - 產生的 C++ thunk(Rust 程式碼呼叫並委派給 C++ Protobuf API 的 glue 程式碼)。
  • C++ Lite 核心
    • <與 C++ 核心相同>
  • UPB 核心
    • .u.pb.rs - 產生的 Rust 程式碼。
      (但是,rust_proto_library 依賴於 upb_proto_aspect 產生的 .thunks.c 檔案。)

如果 proto_library 包含多個檔案,則第一個檔案會宣告為「主要」檔案,並被視為 crate 的進入點;該檔案將包含對應於 .proto 檔案的 gencode,以及對應於所有「次要」檔案的所有符號的重新匯出。

套件

與大多數其他語言不同,.proto 檔案中的 package 宣告未在 Rust codegen 中使用。相反地,每個 rust_proto_library(name = "some_rust_proto") 目標都會發出一個名為 some_rust_proto 的 crate,其中包含目標中所有 .proto 檔案的產生程式碼。

訊息

給定訊息宣告

message Foo {}

編譯器會產生一個名為 Foo 的 struct。Foo struct 定義以下方法

  • fn new() -> Self:建立 Foo 的新執行個體。
  • fn parse(data: &[u8]) -> Result<Self, protobuf::ParseError>:如果 data 保有 Foo 的有效 wire 格式表示法,則將 data 解析為 Foo 的執行個體。否則,此函數會傳回錯誤。
  • fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>:類似於依序呼叫 .clear()parse()
  • fn serialize(&self) -> Result<Vec<u8>, SerializeError>:將訊息序列化為 Protobuf wire 格式。序列化可能會失敗,但很少會發生。失敗原因包括超出最大訊息大小、記憶體不足,以及未設定的必要欄位 (proto2)。
  • fn merge_from(&mut self, other):將 selfother 合併。
  • fn as_view(&self) -> FooView<'_>:傳回 Foo 的不可變控制代碼 (view)。這在關於 proxy 類型的章節中進一步說明。
  • fn as_mut(&mut self) -> FooMut<'_>:傳回 Foo 的可變控制代碼 (mut)。這在關於 proxy 類型的章節中進一步說明。

Foo 實作以下 trait

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

訊息 Proxy 類型

由於需要使用單個 Rust API 支援多個核心,因此在某些情況下我們無法使用原生 Rust 參考 (&T&mut T),而是需要使用類型 (ViewMut) 來表達這些概念。這些情況是共享和可變參考,適用於

  • 訊息
  • 重複欄位
  • Map 欄位

例如,編譯器會發出 struct FooView<'a>FooMut<'msg> 以及 Foo。這些類型用於取代 &Foo&mut Foo,並且它們的行為與原生 Rust 參考在借用檢查器行為方面相同。就像原生借用一樣,View 是 Copy,並且借用檢查器將強制執行您可以在給定時間擁有任意數量的 View 或最多一個 Mut。

為了本文檔的目的,我們專注於描述為擁有的訊息類型 (Foo) 發出的所有方法。具有 &self 接收器的這些函數的子集也將包含在 FooView<'msg> 上。具有 &self&mut self 的這些函數的子集也將包含在 FooMut<'msg> 上。

若要從 View/Mut 類型建立擁有的訊息類型,請呼叫 to_owned(),這會建立深層副本。

巢狀類型

給定訊息宣告

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

除了名為 Foo 的 struct 之外,還會建立一個名為 foo 的模組來包含 Bar 的 struct。類似地,一個名為 bar 的巢狀模組來包含深層巢狀列舉 Baz

pub struct Foo {}

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

欄位

除了上一節中描述的方法之外,通訊協定緩衝區編譯器還會為 .proto 檔案中訊息內定義的每個欄位產生一組存取器方法。

遵循 Rust 風格,這些方法採用小寫/蛇紋命名法,例如 has_foo()clear_foo()。請注意,存取器欄位名稱部分的 capitalization 維持原始 .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)
  • fn set_foo(&mut self, val: i32):設定欄位的值。呼叫此函數後,has_foo() 將傳回 true,而 foo() 將傳回 value
  • fn clear_foo(&mut self):清除欄位的值。呼叫此函數後,has_foo() 將傳回 false,而 foo() 將傳回預設值。

對於其他數值欄位類型(包括 bool),int32 會根據 純量值類型表 替換為對應的 C++ 類型。

隱含存在數值欄位 (proto3)

對於這些欄位定義

int32 foo = 1;
  • fn foo(&self) -> i32:傳回欄位的目前值。如果未設定欄位,則傳回 0
  • fn set_foo(&mut self, val: i32):設定欄位的值。呼叫此函數後,foo() 將傳回 value。

對於其他數值欄位類型(包括 bool),int32 會根據 純量值類型表 替換為對應的 C++ 類型。

選用字串/位元組欄位 (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)
  • 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)
  • 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::Cordabsl::Cord 目前在 Rust 中沒有對等類型。Protobuf Rust 使用列舉來表示 cord 欄位

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

在常見情況下,對於小字串,absl::Cord 將其資料儲存為連續字串。在這種情況下,cord 存取器會傳回 ProtoStringCow::Borrowed。如果底層 absl::Cord 是非連續的,則存取器會將資料從 cord 複製到擁有的 ProtoString,並傳回 ProtoStringCow::OwnedProtoStringCow 實作 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 類型來取代。

選用列舉欄位 (proto2 和 proto3)

給定列舉類型

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

編譯器會產生一個 struct,其中每個變體都是一個關聯常數

#[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)
  • fn set_foo(&mut self, val: Bar):設定欄位的值。呼叫此函數後,has_foo() 將傳回 true,而 foo() 將傳回 value
  • fn clear_foo(&mut self):清除欄位的值。呼叫此函數後,has_foo() 將傳回 false,而 foo() 將傳回預設值。

隱含存在列舉欄位 (proto3)

給定列舉類型

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

編譯器會產生一個 struct,其中每個變體都是一個關聯常數

#[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<'_>:傳回欄位目前值的 view。如果未設定欄位,則傳回空訊息。
  • 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 int32 foo = 1;

編譯器將產生以下存取器方法

  • fn foo(&self) -> RepeatedView<'_, i32>:傳回底層重複欄位的 view。
  • fn foo_mut(&mut self) -> RepeatedMut<'_, i32>:傳回底層重複欄位的可變控制代碼。
  • fn set_foo(&mut self, src: impl IntoProxied<Repeated<i32>>):將底層重複欄位設定為 src 中提供的新重複欄位。

對於不同的欄位類型,只會變更 RepeatedViewRepeatedMutRepeated 類型的各自泛型類型。例如,給定 string 類型的欄位,foo() 存取器將傳回 RepeatedView<'_, ProtoString>

Map 欄位

對於此 map 欄位定義

map<int32, int32> weight = 1;

編譯器將產生以下 3 個存取器方法

  • fn weight(&self) -> protobuf::MapView<'_, i32, i32>:傳回底層 map 的不可變 view。
  • fn weight_mut(&mut self) -> protobuf::MapMut<'_, i32, i32>:傳回底層 map 的可變控制代碼。
  • fn set_weight(&mut self, src: protobuf::IntoProxied<Map<i32, i32>>):將底層 map 設定為 src

對於不同的欄位類型,只會變更 MapViewMapMutMap 類型的各自泛型類型。例如,給定 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;
    ...
}

編譯器將為每個欄位產生存取器(getter、setter、hazzer),就好像同一個欄位在 oneof 之外宣告為 optional 欄位一樣。因此,您可以使用 oneof 欄位,就像使用一般欄位一樣,但是設定一個欄位將清除 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
  }

此外,它將產生兩個存取器

  • fn example_name(&self) -> ExampleNameOneof<_>:傳回列舉變體,指示設定了哪個欄位以及欄位的值。如果未設定任何欄位,則傳回 not_set
  • fn example_name_case(&self) -> ExampleNameCase:傳回列舉變體,指示設定了哪個欄位。如果未設定任何欄位,則傳回 not_set

列舉

給定像這樣的列舉定義

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 目前正在開發中。擴充欄位將透過 parse/serialize 來維護,並且在 C++ 互通案例中,如果從 Rust 存取訊息,則任何設定的擴充功能都將被保留(並且在訊息複製或合併的情況下傳播)。

Arena 配置

Arena 配置訊息的 Rust API 尚未實作。

在內部,upb 核心上的 Protobuf Rust 使用 arena,但在 C++ 核心上則不使用。但是,可以安全地將 C++ 中 arena 配置的訊息的參考(const 和 mutable)傳遞給 Rust 以進行存取或變更。

服務

服務的 Rust API 尚未實作。