anthropics/buffa
GitHub: anthropics/buffa
一个纯 Rust 实现的高性能 Protocol Buffers 库,支持 editions、零拷贝视图解码和 JSON 序列化。
Stars: 479 | Forks: 15
# buffa
一个纯 Rust 实现的 Protocol Buffers 库,提供一流的 [protobuf editions](https://protobuf.dev/editions/overview/) 支持。由 Claude 编写 ❣️
## 为什么选择 buffa?
Rust 生态系统中缺乏一个积极维护、支持 [protobuf editions](https://protobuf.dev/editions/overview/) 的纯 Rust 库。Buffa 填补了这一空白,其从头设计将 editions 视为核心抽象。它通过了当前所有的二进制和 JSON protobuf 序列化一致性测试。
## 功能特性
- **Editions 优先。** Proto2 和 proto3 被理解为 editions 模型中的功能预设。单一代码路径,通过已解析的功能进行参数化。
- **双层 owned/borrowed 类型。** 每个消息都会生成 `MyMessage`(owned,堆分配)和 `MyMessageView<'a>`(从线路零拷贝)。`OwnedView` 将视图与其底层 `Bytes` 缓冲区包装在一起,以便跨 async 边界使用。
- **`MessageField`。** 可选消息字段在未设置时会解引用到默认实例 —— 无需 `Option>` 解包仪式。
- **`EnumValue`。** 类型安全的开放枚举,具有适当的 Rust `enum` 类型并保留未知值,而不是原始的 `i32`。
- **线性时间序列化。** 缓存的编码大小可防止在没有大小缓存遍历的库中出现的指数级膨胀。
- **未知字段保留。** 为代理和中间件用例提供往返保真度。
- **`no_std` + `alloc`。** 核心运行时无需 `std` 即可工作,包括通过 serde 进行 JSON 序列化。启用 `std` 会增加 `std::io` 集成、`std::time` 转换和线程局部 JSON 解析选项。
## Wire 格式
buffa 支持 **binary** 和 **JSON** protobuf 编码:
- **二进制线路格式** -- 全面支持所有标量类型、嵌套消息、重复/打包字段、映射、oneof、组和未知字段。
- **Proto3 JSON** -- 通过可选的 `serde` 集成提供规范的 protobuf JSON 映射。包括知名类型的序列化(Timestamp 为 RFC 3339,Duration 为 `"1.5s"`,int64/uint64 为带引号的字符串,bytes 为 base64 等)。
**不支持文本格式 (`textproto`)**,且不在计划中。
## 不支持的功能
这些是有意排除在范围之外的:
- **文本格式 (`textproto`)** — 不在计划中。二进制和 JSON 是 RPC 和存储的重要线路格式。
- **运行时反射** (`DynamicMessage`,描述符驱动的内省) — 0.1 版本不考虑。Buffa 是一个代码生成优先的库;如果你需要与模式无关的处理,请考虑保留未知字段或使用 `Any`。
- **Proto2 可选字段 getter 方法** — `optional` 字段上的 `[default = X]` 不会生成 `fn field_name(&self) -> T` 解包到默认值的访问器。自定义默认值仅通过 `impl Default` 应用于 `required` 字段。可选字段是 `Option`;请使用模式匹配或 `.unwrap_or(X)`。
- **`no_std` 中的作用域 `JsonParseOptions`** — serde 的 `Deserialize` trait 没有上下文参数,因此运行时选项必须通过环境状态传递。在 `std` 构建中,[`with_json_parse_options`] 通过线程局部变量提供每个闭包、每线程的作用域。在 `no_std` 构建中,[`set_global_json_parse_options`] 通过全局原子变量提供进程范围的一次性配置。这两个 API 是互斥的。`no_std` 全局变量支持单一枚举的 accept-with-default,但不支持重复/映射容器过滤(这需要作用域严格模式覆盖)。
## 已知限制
这些是我们打算在未来的版本中解决的差距:
- **packed-repeated 视图解码中的闭包枚举未知值** 会被静默丢弃(不会路由到未知字段)。owned 解码器可以正确处理此问题;视图解码器可以正确处理单一、可选、oneof 和非打包重复字段。打包 blob 没有可借用的每元素标签,因此零拷贝 `UnknownFieldsView<'a>` 没有可引用的跨度。
- **映射值中的闭包枚举未知值** 会被静默丢弃(不会路由到未知字段)。proto 规范要求将整个映射条目(键 + 值)放入未知字段,这需要重新编码。这会影响具有 `map` 的 proto2 模式,即当演进后的发送方添加新的枚举值时。
## Semver 和 API 稳定性
Buffa 处于 1.0 之前的版本。我们遵循 [Rust 社区约定](https://doc.rust-lang.org/cargo/reference/semver.html) 处理 0.x crates:破坏性变更会增加 **minor** 版本(0.1.x → 0.2.0),增量变更会增加 **patch** 版本(0.1.0 → 0.1.1)。固定到 minor 版本(`buffa = "0.1"`)以避免意外。
生成的代码 API(结构体形状、`Message` trait、`MessageView` trait、`EnumValue`、`MessageField`)被视为主要的稳定性表面。标记为 `#[doc(hidden)]` 的内部辅助模块(`__private`、`__buffa_*` 字段)可能随时更改。
## 快速开始
### 使用 `buf generate`(推荐)
安装 [buf](https://buf.build/docs/installation) 和 [protoc plugins](docs/guide.md#installing-the-protoc-plugins),然后创建一个 `buf.gen.yaml`:
```
version: v2
plugins:
- local: protoc-gen-buffa
out: src/gen
- local: protoc-gen-buffa-packaging
out: src/gen
strategy: all
```
```
buf generate
```
### 在 `build.rs` 中使用 `buffa-build`
或者,使用 `buffa-build` 进行基于 `build.rs` 的工作流(需要 PATH 中有 `protoc`):
```
// build.rs
fn main() {
buffa_build::Config::new()
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();
}
```
### 编码和解码
```
use buffa::Message;
// Encode
let msg = MyMessage { id: 42, name: "hello".into(), ..Default::default() };
let bytes = msg.encode_to_vec();
// Decode (owned)
let decoded = MyMessage::decode_from_slice(&bytes).unwrap();
// Decode (zero-copy view)
let view = MyMessageView::decode_view(&bytes).unwrap();
println!("name: {}", view.name); // &str, no allocation
// Decode (owned view — zero-copy + 'static, for async/RPC use)
let owned_view = OwnedView::::decode(bytes.into()).unwrap();
println!("name: {}", owned_view.name); // still zero-copy, but 'static + Send
```
### JSON 序列化(启用 `json` feature)
```
let json = serde_json::to_string(&msg).unwrap();
let decoded: MyMessage = serde_json::from_str(&json).unwrap();
```
## 文档
- **[用户指南](docs/guide.md)** — 关于 buffa 的 API、生成的代码形状、编码/解码、视图、JSON、知名类型和 editions 支持的综合指南。
- **[从 prost 迁移](docs/migration-from-prost.md)** — 包含前后代码示例的分步迁移指南。
- **[从 protobuf 迁移](docs/migration-from-protobuf.md)** — 涵盖 stepancheg v3 和 Google 官方 v4 的迁移指南。
## 工作区布局
| Crate | 用途 |
|---|---|
| `buffa` | 核心运行时:`Message` trait,线路格式编解码器,`no_std` 支持 |
| `buffa-types` | 知名类型:Timestamp, Duration, Any, Struct, wrappers 等 |
| `buffa-descriptor` | Protobuf 描述符类型(`FileDescriptorProto`, `DescriptorProto`, ...) |
| `buffa-codegen` | 从 protobuf 描述符生成代码 |
| `buffa-build` | 通过 `protoc` 调用代码生成的 `build.rs` 辅助工具 |
| `protoc-gen-buffa` | `protoc` 插件二进制文件 |
## 性能
在 Intel Xeon Platinum 8488C (x86_64) 上测量的四种代表性消息类型的吞吐量比较。跨实现基准测试在 Docker 中运行以保持工具链一致性(`task bench-cross`)。越高越好。
### 二进制解码

### 二进制编码

### JSON 编码

### JSON 解码

**消息类型:** ApiResponse(约 200 B,扁平标量),LogRecord(约 1 KB,字符串 + 映射 + 嵌套消息),AnalyticsEvent(约 10 KB,深度嵌套 + 重复子消息),GoogleMessage1(标准 protobuf 基准消息)。
**库:** prost 0.13 + pbjson 0.7,protobuf‑v4(Google Rust/upb,v4.33.1),Go `google.golang.org/protobuf` v1.36.6。protobuf-v4 JSON 未包含在内,因为它不提供 JSON 编解码器。
**Owned 解码权衡:** buffa 的 owned 解码通常在 prost 的 ±10% 范围内,以较小的吞吐量成本换取 prost 省略的功能:默认保留未知字段、类型化的 `EnumValue` 包装器(而非原始 `i32`),以及支持递归消息类型而无需手动装箱的类型稳定解码循环。零拷贝视图路径(`MyMessageView::decode_view`)完全避免了分配,是推荐的快速解码路径。protobuf-v4 在深度嵌套消息上的解码优势来自于 upb 的 arena 分配器 —— 所有子消息都在一个 arena 中进行 bump 分配,而不是单独装箱。
## 一致性
buffa 通过了 protobuf 二进制和 JSON 一致性测试套件(v33.5,editions 最多到 2024)。`std` 和 `no_std` 构建都通过了包括 JSON 在内的完整套件。使用 `task conformance` 运行。
## 编译器兼容性
**[buf](https://buf.build/docs/cli/)** 是编译 `.proto` 文件的推荐方式。buf CLI 有自己的内置编译器,因此不需要单独安装 `protoc` —— 只需安装 buf 和 `protoc-gen-buffa`。
**protoc** 也得到完全支持。`protoc-gen-buffa` 和 `buffa-build` 适用于 **protoc v21.12 及更高版本**。最低版本因功能而异:
| 功能 | 最低 protoc |
|---|---|
| Proto2 + proto3 | v21.12 |
| Editions 2023 | v27.0 |
| Editions 2024 | v33.0 |
请注意,Linux 发行版包(Debian Bookworm,Ubuntu 24.04)附带的 protoc v21.12 不支持 editions。如果你需要 editions 支持,请从 [GitHub releases]() 安装 protoc v27+ 或使用 buf。
兼容性针对 protoc v21.12、v22.5、v25.5、v27.3、v29.5 和 v33.5 进行了测试(`task protoc-compat`)。
## 最低支持的 Rust 版本
1.85
## 许可证
Apache-2.0
原始数据 (MiB/s)
| Message | buffa | buffa (view) | prost | protobuf‑v4 | Go | |---------|------:|-------------:|------:|------------:|---:| | ApiResponse | 762 | 1,245 (+63%) | 777 (+2%) | 720 (−5%) | 277 (−64%) | | LogRecord | 689 | 1,772 (+157%) | 692 (+0%) | 882 (+28%) | 251 (−64%) | | AnalyticsEvent | 188 | 307 (+63%) | 258 (+37%) | 364 (+93%) | 92 (−51%) | | GoogleMessage1 | 801 | 1,093 (+36%) | 1,001 (+25%) | 659 (−18%) | 351 (−56%) |原始数据 (MiB/s)
| Message | buffa | prost | protobuf‑v4 | Go | |---------|------:|------:|------------:|---:| | ApiResponse | 2,637 | 1,755 (−33%) | 1,050 (−60%) | 570 (−78%) | | LogRecord | 4,149 | 3,163 (−24%) | 1,717 (−59%) | 309 (−93%) | | AnalyticsEvent | 671 | 369 (−45%) | 516 (−23%) | 162 (−76%) | | GoogleMessage1 | 2,543 | 1,866 (−27%) | 882 (−65%) | 366 (−86%) |原始数据 (MiB/s)
| Message | buffa | prost | Go | |---------|------:|------:|---:| | ApiResponse | 869 | 776 (−11%) | 119 (−86%) | | LogRecord | 1,335 | 1,099 (−18%) | 144 (−89%) | | AnalyticsEvent | 781 | 768 (−2%) | 52 (−93%) | | GoogleMessage1 | 1,047 | 840 (−20%) | 129 (−88%) |原始数据 (MiB/s)
| Message | buffa | prost | Go | |---------|------:|------:|---:| | ApiResponse | 721 | 299 (−59%) | 71 (−90%) | | LogRecord | 780 | 694 (−11%) | 112 (−86%) | | AnalyticsEvent | 272 | 239 (−12%) | 47 (−83%) | | GoogleMessage1 | 635 | 253 (−60%) | 74 (−88%) |标签:API, JSON, no_std, Proto2, Proto3, Protobuf, Protocol Buffers, RPC, Rust, Serde, SOC Prime, Zero-Copy, 中间件, 代码生成, 内存安全, 内核驱动, 可视化界面, 嵌入式系统, 并发, 序列化, 开发工具, 开源库, 异步, 性能优化, 搜索引擎爬虫, 数据序列化, 数据结构, 数据编码, 无标准库, 检测绕过, 消息传递, 渗透测试工具, 网络协议, 网络流量审计, 通知系统, 零拷贝