ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

5分钟跑通prost:Rust协议缓冲区序列化快速上手指南

5分钟跑通prost:Rust协议缓冲区序列化快速上手指南

5分钟跑通prost:Rust协议缓冲区序列化快速上手指南

【免费下载链接】prostPROST! a Protocol Buffers implementation for the Rust Language项目地址: https://gitcode.com/GitHub_Trending/pr/prost

后端服务和 Rust 客户端想共享一套数据结构,用 JSON 又臃肿、解析代码又多。protobuf 是谷歌设计的一套紧凑二进制序列化协议——序列化就是把结构化数据压成一串可传输的字节,反序列化再还原回来。prost 是 Rust 生态里最常用的 protobuf 实现,从.proto文件直接生成地道的 Rust 类型。这篇教程带你从空项目开始,5 分钟左右跑通第一遍 encode / decode。只要写过一点 Rust,没碰过 prost 也完全能跟上。

为什么选 prost

  • 生成的代码就是普通 Rust 类型:message 变 struct,enum 变 enum,oneof 也变 enum,全靠 derive 宏生成,拿到手就能当普通类型用。
  • 基于 bytes 字节缓冲做编解码:接口围绕bytes::Buf/BufMut,而不是std::io,在网络程序、内存零拷贝场景里用着更顺手。
  • 轻:没有运行时反射、没有描述符对象,核心库依赖极少,还支持no_std。

代价是它不做运行时自省,字段元信息在编译期就固化进代码了。对大多数"定好 schema 就稳定跑"的场景,这反而是优点。

依赖怎么配,构建脚本怎么写

装依赖前有个前提:prost-build从 0.11 起不再自带protoc(protobuf 官方编译器),系统里必须先装好protoc并加入 PATH,否则构建会直接失败。

然后建一个 Cargo 项目,在Cargo.toml里加三样东西:

[dependencies] prost = "0.14" prost-types = "0.14" [build-dependencies] prost-build = "0.14"

prost是运行时库;prost-types提供 Timestamp 这类 well-known types(protobuf 预定义的常用类型),不用到时可以不装;prost-build只在构建期工作,负责把.proto变成 Rust 代码。

新建proto/reading.proto,定义一条传感器读数:

syntax = "proto3"; message Reading { string sensor_id = 1; double value = 2; repeated string tags = 3; }

注意字段后面的数字是 tag,protobuf 靠它在字节流里区分字段。tag 一旦发出去就不要改、不要复用。

再写构建脚本build.rs:

fn main() -> Result<(), Box<dyn std::error::Error>> { prost_build::compile_protos("proto/reading.proto")?; Ok(()) }

就一行核心逻辑:调用protoc编译 proto,把生成的reading.rs放进 Cargo 约定的OUT_DIR。

第一条序列化语句怎么写

src/main.rs里写最小例子,目标:造一个 Reading,编码成字节,再解码回来对答案:

use prost::Message; include!(concat!(env!("OUT_DIR"), "/reading.rs")); fn main() { let reading = Reading { sensor_id: "temp-a1".into(), value: 23.5, tags: vec!["kitchen".into(), "indoors".into()], }; let bytes = reading.encode_to_vec(); println!("wire bytes: {:?}", bytes); let back = Reading::decode(bytes.as_slice()).unwrap(); println!("decoded: {back:?}"); assert_eq!(reading, back); }

逐段说:

  • include!那行把构建时生成的代码原样拉进当前文件,Reading这个 struct 就是它带来的。
  • encode_to_vec()把 struct 压成Vec<u8>,这就是发往对端的字节流。
  • Reading::decode反过来,从字节流还原出 struct;整个 buffer 会被消费完。
  • assert_eq!能过,说明 encode 和 decode 互为逆操作。

cargo run后你会看到一行紧凑的 wire bytes(开头几个字节就是 tag 加类型信息),紧跟一行解码结果,程序安静退出、断言不炸。

往真实场景走一步:带时间戳的事件日志

实际系统很少一次发一条读数,通常是一批读数打个包,再挂个时间戳。把reading.proto改成:

syntax = "proto3"; import "google/protobuf/timestamp.proto"; message Reading { string sensor_id = 1; double value = 2; repeated string tags = 3; } message EventLog { repeated Reading readings = 1; google.protobuf.Timestamp recorded_at = 2; }

main.rs跟着改:

use prost::Message; use prost_types::Timestamp; include!(concat!(env!("OUT_DIR"), "/reading.rs")); fn main() { let mut log = EventLog::default(); log.readings.push(Reading { sensor_id: "temp-a1".into(), value: 23.5, tags: vec!["kitchen".into()], }); log.recorded_at = Some(Timestamp { seconds: 1_760_000_000, nanos: 0 }); let payload = log.encode_to_vec(); let parsed = EventLog::decode(payload.as_slice()).unwrap(); println!( "events: {}, at {}s", parsed.readings.len(), parsed.recorded_at.as_ref().unwrap().seconds ); }

两个关键点:

  • Timestamp不用自己写 proto,prost-types直接给出对应的 Rust 类型,import一句就能引用。
  • proto3 里 message 类型字段生成的 Rust 字段是Option<T>,所以recorded_at要包一层Some。这也是 prost 的风格:把"字段可能缺失"直接暴露成类型系统里的Option。

再往后走,如果字段之间互斥(比如事件来源是"用户操作"或"定时任务"二选一),proto 里用oneof,prost 会把它生成成一个带变体的 Rust enum,match 起来非常自然。

常见坑与排错

现象:cargo build时prost_build报找不到protoc之类的错误。原因:prost-build 早已不捆绑编译器,它只会去 PATH 里找protoc。解决:从 protobuf 官方渠道安装protoc,确认protoc --version有输出,再重新构建。

现象:decode返回Err,信息类似 unexpected wire type 或 out of range。原因:喂进去的字节流不是这条 schema 的完整消息——常见是流被截断,或收发两端用的.proto版本不一致。解决:先确认两端 schema 一致;网络收流时注意攒够完整长度再调decode,别拿半包数据硬解。

现象:对端没设置的字段,解出来是 0 或空串,分不清"没发"和"发了个零"。原因:proto3 的标量字段默认没有 presence(存在性)概念,缺失时填默认值。解决:在 proto 里给该字段加optional修饰符,生成的 Rust 字段就会变成Option<T>。

收尾:接下来往哪看

到这里,你已经走完了 prost 的核心闭环:写.proto→prost-build生成代码 →encode_to_vec压成字节 →decode还原,还顺手用上了 well-known typeTimestamp。再深入的话,按这个顺序翻仓库源码最高效:

  • prost/src/message.rs:Messagetrait 的完整定义,encode、decode、encode_length_delimited这些方法都实现在这,读一遍就知道编解码的底层路径。
  • prost/src/lib.rs:库的入口,能看到默认开启的std/derivefeature 和对外暴露的错误类型。
  • tests/src/well_known_types.rs:Timestamp 等 well-known types 在真实用例里怎么参与 encode/decode 的完整示范。

把这三处看完,你基本能脱离教程自己排查问题、自己扩展 schema 了。

【免费下载链接】prostPROST! a Protocol Buffers implementation for the Rust Language项目地址: https://gitcode.com/GitHub_Trending/pr/prost

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进