pbwire 0.1.6

Rust binding over the pbwire C ABI: dynamic protobuf without generated code
docs.rs failed to build pbwire-0.1.6
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

pbwire Rust binding

bindings/rust is the Rust binding: a safe wrapper over the C ABI in include/pbwire.h, linked as libpbwire.a. A FileDescriptorSet (protoc --descriptor_set_out) becomes a schema at run time; there is no generated code, no build.rs codegen and no bindgen step. Wire bytes are the core's, so a Rust message and a C message are the same bytes by construction.

use pbwire::Schema;

let schema = Schema::from_file("tests/data/matrix3.desc")?;
let ty = schema.message("structtest.Scalars3")?;
let inner_ty = schema.message("structtest.Inner3")?;

let mut msg = ty.new_message();
msg.set_i64(ty.field("i32")?, -42)
    .set_str(ty.field("str")?, "alice");
for tag in ["a", "bb"] {
    msg.set_str(ty.field("tags")?, tag);          // repeated: appends
}
msg.set_message(ty.field("inner")?)              // nested: created in place
    .set_str(inner_ty.field("name")?, "kid")
    .set_i64(inner_ty.field("value")?, 42);

let wire = msg.encode()?;
let decoded = ty.decode(&wire)?;                 // unknown fields preserved
assert_eq!(decoded.encode()?, wire);             // byte-exact round trip
# Ok::<(), pbwire::Error>(())

A Field is resolved once (ty.field("i32")) and reused, like the Java binding's cached FieldInfo: a set_*/get_* call is one native call, not a name lookup.

Build and run

make -C ../..            # build/libpbwire.a, the archive the crate links
cargo test               # self-check against the pinned protoc bytes
cargo run --example roundtrip -- ../c/tests/data/matrix3.desc

build.rs copies build/libpbwire.a into OUT_DIR and links it statically, so the resulting binary has no runtime dependency on the library. PBWIRE_LIB_DIR points at a different build directory. Everything is cdylib-free on purpose: the FFI hosts (Java, Node, Objective-C, PHP, Dart, C#) dlopen the shared library, while Rust compiles the archive in.

Benchmarks

./benchmarks/bench.sh    # vs rust-protobuf 3.x generated code + prost, four tiers

Needs cargo install protobuf-codegen (it provides protoc-gen-rs and the runtime the generated code uses); the prost baseline generates at build time from crates.io. The four tiers are small / medium / large / xlarge (the repeated fields scaled x0 / x1 / x100 / x3000). Copied from BENCHMARKS.md, which is the single source for all performance numbers; higher is better, MB/s over wire bytes:

payload op pbwire-rust (MB/s, x protobuf-rust) protobuf-rust (MB/s) prost (MB/s)
136 B encode 1830.6 (1.39x) 1319.7 1592.0
136 B decode 943.2 (1.24x) 760.7 924.6
174 B encode 1736.3 (1.28x) 1358.7 1545.9
174 B decode 821.0 (1.40x) 587.5 657.6
2469 B encode 1696.1 (2.05x) 828.5 1470.1
2469 B decode 637.1 (1.70x) 374.0 355.6
74913 B encode 1596.5 (1.84x) 866.2 1468.1
74913 B decode 669.0 (1.69x) 396.4 407.7

set_str rejects a string containing a NUL byte (it would truncate on the way to C); use set_bytes for binary payloads, including on a string field.

Threads

A Schema is immutable once loaded, so &Schema can be shared. A Message is not Send/Sync on purpose: like the C core, one tree belongs to one thread.

Typed models

protoc-gen-pbwire-typed (in ../typed) generates structs with typed fields, encode(&Schema) / decode(&Schema, &[u8]) and encode_into for nesting, all resolving fields through the schema. The committed output is tests/gen/ and examples/gen/; examples/roundtrip.rs assigns typed fields only. See ../typed/README.md.