Continuing the discussion from Overwhelmed by the vast variety of serialization formats. Which to use when?:
After spending more time with quick-protobuf
(using pb-rs
as code generator) and prost
(using prost-build
as a code generator), I must say I'm not really happy with either of those.
quick-protobuf
and pb-rs
pb-rs
has a command line tool, which I like. And it also provides options for whether you want to work with Cow
s or not. However, the generated code isn't well readable, and you can't (reasonably) manually write a Rust struct for a Protobuf message if I understand it right. It's also possible to use pb-rs
in your build.rs
, but I didn't get that to work yet and the example how to use build.rs
seems to be erroneous:
mod hello { include_bytes!(concat!(env!("OUT_DIR")), "/hello.rs"); }
This is a syntax error (Playground), see include_bytes!
. Also using include_bytes!
doesn't really make sense to me. It should be include!
. And the closing parenthesis is wrong. But even if I fix this, I get errors:
mod hello {
include!(concat!(env!("OUT_DIR"), "/protos/hello.rs"));
}
% cargo build
Compiling mycrate v0.0.0 (/usr/home/jbe/mycrate)
error: an inner attribute is not permitted in this context
--> /usr/home/jbe/mycrate/target/debug/build/mycrate-…/out/protos/hello.rs:3:1
|
3 | #![allow(non_snake_case)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
...
12 | use std::borrow::Cow;
| --------------------- the inner attribute doesn't annotate this `use` import
|
= note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files
help: to annotate the `use` import, change the attribute from inner to outer style
|
3 - #![allow(non_snake_case)]
3 + #[allow(non_snake_case)]
This is confusing, but looks like include!
doesn't allow you to include a file which has #![…]
annotations (even if you could paste the file's contents where the include
! is located). Is that a bug in Rust or just not well documented? Or do I misunderstand how include!
works?
This seems to work, but it's somewhat awkward:
mod hello {
mod private {
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
}
pub use private::hello::*;
}
Overall, I feel like it's not well documentated how to use pb-rs
, or I was just looking in the wrong places.
prost
and prost-build
prost-build
doesn't even have a command line tool, it seems. If I understand it right, I can either use build.rs
or write my struct
s and enum
s manually.
Writing them manually is tough because the required annotations don't seem to be documented anywhere. At least I didn't find documentation on that matter.
Consider I have the following .proto
file (with App
defined elsewhere):
message Boot {
map<string, App> apps = 1;
}
Then I could manually turn this into Rust code as follows:
use prost::Message;
use std::collections::HashMap;
#[derive(Clone, PartialEq, Eq, Message)]
pub struct Boot {
#[prost(map = "string, message", tag = "1")]
pub apps: HashMap<String, App>,
}
This looks very clean, and I like it. But I had to reverse engineer the output created by prost-build
in order to figure out that I have to write map = "string, message"
for example. (At least I didn't find it in the docs.)
Moreover, making a tiny syntax error with the annotations gives you horrible errors with a backtrace:
error: proc-macro derive panicked
--> src/lib.rs:13:32
|
13 | #[derive(Clone, PartialEq, Eq, Message)]
| ^^^^^^^
|
= help: message: called `Result::unwrap()` on an `Err` value: invalid message field Boot.apps
Caused by:
no type attribute
Stack backtrace:
Not nice.
I also tried using a build.rs
file, creating the Rust struct
automatically:
use std::io::Result;
fn main() -> Result<()> {
prost_build::compile_protos(&["protobuf/boot.proto"], &["protobuf/"])?;
Ok(())
}
However, as my .proto
file doesn't use Protobuf's package feature (opposed to the example), the compile_protos
function will simply create _.rs
in the topmost out/
directory, thus that I have to include the generated struct
s as follows:
mod my_protobuf_datatypes {
include!(concat!(env!("OUT_DIR"), "/_.rs"));
}
This can't be right in regard to namespace! prost-build
shouldn't occupy the "_
" name in the topmost directory. When I use a package
specifier like in the example, things aren't much better, as I guess I'm supposed to name the package in the same name as my crate, so it isn't really much separated either (i.e. I mean it would be stupid if I had to prefix the protobuf package name with "protobuf" just to get the namespacing right).
Overall, I'm unhappy with either of those two solutions. What would you recommend to do? I feel like Protobuf is too complex to make a quick (and reasonably good) implementation on my own. Has my approach to use build.rs
in either of these two cases non-idiomatic? Is someone using either of these crates in production and what are your experiences and how did you get it to work properly?