The protobuf toolchain just lost its C++ dependency.
Today we’re releasing OxiProto 0.1.3 — the COOLJAPAN Pure-Rust Protocol Buffers stack: a native .proto parser, code generator, and wire runtime that you can drop into [build-dependencies] and never think about protoc again.
No protoc. No FFI. No -sys crates. No apt-get install protobuf-compiler. Pure Rust from the .proto file all the way to the encoded bytes.
Why OxiProto
Protocol Buffers are everywhere — gRPC services, event streams, config formats, on-disk records. But in Rust, every one of those use cases historically dragged in the same hidden prerequisite: the protoc C++ compiler binary, sitting on PATH, demanded by prost-build and tonic-build at build time.
That single requirement quietly poisons a lot of pipelines. A stock rust:slim container can’t build your crate until you bolt on a system package. Cross-compilation needs a pre-stage. CI images grow. Reproducibility suffers, because now your build depends on whatever protoc version the host happens to ship. And it’s all for a step that is, conceptually, just parsing a text grammar and emitting some structs.
OxiProto removes that step. A consumer with oxiproto = "0.1.3" in [build-dependencies] regenerates protobuf bindings on a stock rust:slim image — no protobuf compiler, no cross-compile pre-stage, no Bazel toolchain. All .proto parsing and descriptor construction runs through a native pure-Rust parser, and the already-pure prost runtime is re-exported as the wire-format engine.
This is the NoFFI thesis applied to serialization: take a piece of infrastructure that everyone assumed had to be C++, and rebuild it in memory-safe Rust so the default build is a single toolchain with no native dependencies.
What we built
OxiProto is a small workspace of focused crates. Each one does one layer of the protobuf stack, and the top-level oxiproto facade re-exports them behind feature flags so you only compile what you use.
1. Core types and the native wire format — oxiproto-core
The foundation: a complete varint / zigzag / tag / fixed / length-delimited codec in oxiproto-core::wire, independent of prost. It’s no_std + alloc compatible when you turn off the default std feature. This crate also defines the OxiMessage, OxiName, and OxiOneof traits and the Extensions machinery.
2. The build helper — oxiproto-build
The crate you put in [build-dependencies]. It turns .proto files into a FileDescriptorSet using a native pure-Rust parser by default, with protox (also pure Rust) as a fallback. The native parser handles proto2 and proto3, multi-file import resolution, source_code_info, custom options, and group desugaring.
3. Codegen — oxiproto-codegen
Generates plain Rust structs and enums — plus optional impl OxiMessage blocks — directly from a FileDescriptorSet. Maps, oneofs, Default, services, doc comments, and JSON support are all covered.
4. Reflection — oxiproto-reflect
Runtime reflection via a prost-reflect facade plus a native DescriptorPool and DynamicMessage, so you can inspect and manipulate messages whose types you don’t know at compile time.
5. Well-Known Types — oxiproto-wkt
Interop for the standard WKTs: Timestamp, Duration, Any, Struct, FieldMask, and the scalar wrappers, with optional chrono interop.
6. JSON — oxiproto-json
The canonical Protobuf-JSON mapping: camelCase field names, base64-encoded bytes, RFC 3339 timestamps.
7. CLI — oxiproto-cli
A standalone command-line tool with gen, describe, encode, decode, format, lint, breaking, and doc subcommands — a protoc-free way to drive code generation and inspect schemas from the shell.
Getting Started
Add the runtime to your dependencies and the build helper to your build dependencies:
[dependencies]
oxiproto = "0.1.3"
[build-dependencies]
oxiproto-build = "0.1.3"
Then compile your schemas in build.rs — no protoc anywhere:
fn main() {
oxiproto_build::compile_protos(&["proto/my_service.proto"], &["proto/"]).unwrap();
}
Prefer to compile a schema inline (great for tests and tooling)? Use the string API, which returns a FileDescriptorSet:
use oxiproto_build::compile_str;
let fds = compile_str(r#"
syntax = "proto3";
message Foo { string name = 1; }
"#)?;
And if you just want to encode and decode bytes by hand, the native wire codec stands on its own:
use oxiproto_core::wire::{EncodeBuffer, DecodeBuffer, encode_varint};
let mut buf = EncodeBuffer::new();
encode_varint(300, &mut buf.inner_mut());
let bytes = buf.into_bytes();
let mut decode = DecodeBuffer::from_bytes(&bytes);
let value = decode.read_varint()?;
assert_eq!(value, 300);
Highlights
- Native pure-Rust
.protoparser — proto2 + proto3, multi-file imports,source_code_info, custom options, group desugaring. protoc-free builds — dropoxiproto-buildinto[build-dependencies]and regenerate bindings on a barerust:slimcontainer.- Standalone wire codec — varint / zigzag / tag / fixed / length-delimited,
no_std+allocfriendly. OxiMessagetrait — native encode/decode (encode_to_vec,decode) on any type, no prost macros required.- Runtime reflection — native
DescriptorPoolandDynamicMessagefor dynamic message handling. - Canonical Protobuf-JSON — camelCase, base64 bytes, RFC 3339 timestamps via
oxiproto-json. - A real CLI —
gen,describe,encode,decode,format,lint,breaking, anddoc. - prost-compatible runtime — the proven, already-pure
prostengine carries the wire format.
Tips
- Pick only the layers you need. Everything beyond the core is feature-gated and off by default: turn on
build,reflect,wkt,codegen, orjsonon theoxiprotofacade as your project requires. - Going
no_std? Disable the defaultstdfeature andoxiproto-core::wirekeeps working with justalloc. - Want native impls instead of prost macros? Have
oxiproto-codegenemitimpl OxiMessage for Tby settingemit_oxi_message_impl = true, then callmy_msg.encode_to_vec()andMyMessage::decode(&encoded)?directly. - Need chrono on your timestamps? Enable
wkt-chronoto get chrono interop onTimestampExt/DurationExton top of thewktfeature. - Round-trip from the shell.
oxiproto-cli encode --schema proto/my.proto --message my.Foo '{"name": "hello"}'turns JSON into proto binary, andoxiproto-cli decodereverses it — handy for debugging wire payloads without writing code. - Edge cases happen. The default native parser covers proto2 + proto3; if you ever hit a construct it doesn’t handle, the pure-Rust
protoxfallback is there whennative-parseris off — still no C++ on the build path.
Part of the COOLJAPAN ecosystem
OxiProto is part of NoFFI — the COOLJAPAN initiative to replace every C/C++/Fortran/-sys FFI dependency in the Rust ecosystem with a clean, memory-safe, 100% Pure Rust implementation. Here the target is protoc: the C++ protobuf compiler, plus the protoc invocations baked into prost-build and tonic-build. Default features are 100% Pure Rust — no C, C++, or Fortran in the default set, and no protobuf-compiler system package needed.
Within the ecosystem, OxiProto depends on nothing else and sits at the bottom of the stack: it’s the foundation for OxiRPC (gRPC) and any future crate that speaks the protobuf wire format. It sits alongside the broader oxi* family across the sovereign Rust stack.
Repository: https://github.com/cool-japan/oxiproto
Star the repo if you want a protobuf toolchain that builds anywhere Rust does. 🦀
Pure Rust serialization — sovereign, safe, and FFI-free.
— KitaSan at COOLJAPAN OÜ June 20, 2026