quick-protobuf

A pure Rust library to serialize/deserialize protobuf files.

Documentation

Description

This library intends to provide a simple yet fast (minimal allocations) protobuf parser implementation.

It provides both: - pb-rs, a code generation tool: - each .proto file will generate a minimal rust module (one function to read, one to write, and one to compute the size of the messages) - each message will generate a rust struct where:

| **Proto**                    | **Rust**                |
|------------------------------|-------------------------|
| bytes                        | `Cow<'a, [u8]>`         |
| string                       | `Cow<'a, str>`          |
| other scalars                | rust primitive          |
| repeated                     | `Vec`                   |
| repeated, packed, fixed size | `Cow<'a, [M]>`          |
| optional                     | `Option`                |
| message                      | `struct`                |
| enum                         | `enum`                  |
| map                          | `HashMap`               |
| oneof Name                   | `OneOfName` enum        |
| nested `m1`                  | `mod_m1` module         |
| package `a.b`                | `mod_a::mod_b` modules  |
| import file_a.proto          | `use super::file_a::*` |

Example: protobuf_example project

```sh cargo install pb-rs pb-rs /path/to/your/protobuf/file.proto

will generate a

/path/to/your/protobuf/file.rs

```

```toml

Cargo.toml

[dependencies] quick-protobuf = "0.6.0" ```

```rust extern crate quick_protobuf;

mod foo_bar; // (see 1.)

use quick_protobuf::Reader;

// We will suppose here that Foo and Bar are two messages defined in the .proto file // and converted into rust structs // // FooBar is the root message defined like this: // message FooBar { // repeated Foo foos = 1; // repeated Bar bars = 2; // } // FooBar is a message generated from a proto file // in parcicular it contains a from_reader function use foobar::FooBar; use quickprotobuf::{MessageRead, BytesReader};

fn main() { // bytes is a buffer on the data we want to deserialize // typically bytes is read from a Read: // r.readtoend(&mut bytes).expect("cannot read bytes"); let mut bytes: Vec; # bytes = vec![];

// we can build a bytes reader directly out of the bytes
let mut reader = BytesReader::from_bytes(&bytes);

// now using the generated module decoding is as easy as:
let foobar = FooBar::from_reader(&mut reader, &bytes).expect("Cannot read FooBar");

// if instead the buffer contains a length delimited stream of message we could use:
// while !r.is_eof() {
//     let foobar: FooBar = r.read_message(&bytes).expect(...);
//     ...
// }
println!("Found {} foos and {} bars", foobar.foos.len(), foobar.bars.len());

} ```

Examples directory

You can find basic examples in the examples directory. - codegen_example: A basic write/read loop on all datatypes

Message <-> struct

The best way to check for all kind of generated code is to look for the codegenexample data: - definition: datatypes.proto - generated code: data_types.rs

Proto definition

``` enum FooEnum { FIRSTVALUE = 1; SECONDVALUE = 2; }

message BarMessage { required int32 brequiredint32 = 1; }

message FooMessage { optional int32 fint32 = 1; optional int64 fint64 = 2; optional uint32 fuint32 = 3; optional uint64 fuint64 = 4; optional sint32 fsint32 = 5; optional sint64 fsint64 = 6; optional bool fbool = 7; optional FooEnum fFooEnum = 8; optional fixed64 ffixed64 = 9; optional sfixed64 fsfixed64 = 10; optional fixed32 ffixed32 = 11; optional sfixed32 fsfixed32 = 12; optional double fdouble = 13; optional float ffloat = 14; optional bytes fbytes = 15; optional string fstring = 16; optional FooMessage fselfmessage = 17; optional BarMessage fbarmessage = 18; repeated int32 frepeatedint32 = 19; repeated int32 frepeatedpacked_int32 = 20 [ packed = true ]; } ```

Generated structs

```rust

[derive(Debug, PartialEq, Eq, Clone, Copy)]

pub enum FooEnum { FIRSTVALUE = 1, SECONDVALUE = 2, }

[derive(Debug, Default, PartialEq, Clone)]

pub struct BarMessage { // all fields are owned: no lifetime parameter pub brequiredint32: i32, }

[derive(Debug, Default, PartialEq, Clone)]

pub struct FooMessage<'a> { // has borrowed fields: lifetime parameter pub fint32: Option, pub fint64: Option, pub fuint32: Option, pub fuint64: Option, pub fsint32: Option, pub fsint64: Option, pub fbool: Option, pub fFooEnum: Option, pub ffixed64: Option, pub fsfixed64: Option, pub ffixed32: Option, pub fsfixed32: Option, pub fdouble: Option, pub ffloat: Option, pub fbytes: Option Cow<[u8]> pub fstring: Option> // string -> Cow pub fselfmessage: Option>>, // reference cycle -> Boxed message pub fbarmessage: Option, pub frepeatedint32: Vec, // repeated: Vec pub frepeatedpacked_int32: Vec, // repeated packed: Vec } ```

Leverage rust module system

Nested Messages

message A { message B { // ... } }

As rust does not allow a struct and a module to share the same name, we use mod_Name for the nested messages. ```rust pub struct A { //... }

pub mod mod_A { pub struct B { // ... } } ```

Package

package a.b;

Here we could have used the same name, but for consistency with nested messages, modules are prefixed with mod_ as well. rust pub mod mod_a { pub mod mod_b { // ... } }

Why not rust-protobuf

This library is an alternative to the widely used rust-protobuf.

Pros / Cons

Codegen

Have a look at the different generated modules for the same .proto file: - rust-protobuf: 2371 loc - quick-protobuf: 302 loc

Benchmarks

An adaptation of rust-protobuf perftest is available and show, on these particular examples, that quick-protobuf is much faster than rust-protobuf.

Contribution

Any help is welcomed! (Pull requests of course, bug report, missing functionality etc...)

Licence

MIT