以前、以下の記事たちを書きました。
Rustで0byteを読み込むとどうなるのか(Option
型になるのか)気になったので検証してみます。
0byteをrust-protobufで読み込む
protobufを読み込むコードは、Golangで出力したprotobufバイナリをRustで読み込む - 逆さまにしたとほぼ同じです。一点変更しているのは、読み込むファイル名で、main.rs
のgo_user.bin
をzero.bin
に変更しました。
ちなみに.proto
の定義は以下の通りです。
syntax = "proto3"; package user; message User { string name = 1; int32 age = 2; }
空ファイルを作成し、それを読み込んだ実行結果は以下の通りです。 Goでいうゼロ値でデコードできています。
❯ touch zero.bin
❯ cargo run
Name:
Age: 0
というのもproto3の仕様に以下のように書いてあるのです。
Unknown fields are well-formed protocol buffer serialized data representing fields that the parser does not recognize. For example, when an old binary parses data sent by a new binary with new fields, those new fields become unknown fields in the old binary.
つまりprotobufでは、unknow fieldsも含めて仕様化されており、パーサーは値が存在しないのか、フィールドが定義されていないのかは区別できないのです。
ゼロ値ならバイナリにすらエンコードされないので、送るデータそのものを無くすことができる効率性があります。
protocで生成したコードの実装
protobufの仕様からGoのゼロ値のようにデフォルト値が使われることがわかりました。
もう一歩踏み込んで、protoc --rustout
でgenerateした実装を見ていきましょう。
わかりやすところで、Golangで出力したprotobufバイナリをRustで読み込む - 逆さまにしたで利用した以下のget
メソッドの実装を見ていきます。
println!("Name: {}", u.get_name()); println!("Age: {}", u.get_age());
protoc --rustout
でgenerateした実装を確認すると、以下のようになっています。
pub fn get_name(&self) -> &str { &self.name } pub fn get_age(&self) -> i32 { self.age }
返り値がOption
に包まれていないですね。
Goの場合はゼロ値がありますが、Rustではどうやって実現されているんでしょうか。
ということでnew
の実装をみてみます。
impl User { pub fn new() -> User { ::std::default::Default::default() } }
予想通りですが、default
が使われています。
なお、User
の定義でもDefault
がattributeに含まれています。
#[derive(PartialEq,Clone,Default)] pub struct User { // message fields pub name: ::std::string::String, pub age: i32, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, }