Open-source project
tokio-rs/prost avatar
tokio-rs/prost

prost: protobuf codegen that hands you structs and leaves protoc to you

PROST! a Protocol Buffers implementation for the Rust Language

4,798 stars658 forksRustApache-2.0

At a glance

What is it?
prost is the Protocol Buffers implementation in the tokio ecosystem, turning proto2 and proto3 files into Rust structs and enums built on derive attributes. Its deliberate omissions shape what you can build with it: no bundled protoc, no runtime reflection, and no way to tell an absent proto3 scalar from its zero value unless you ask for it.
Who is it for?
prost fits a Rust service where the schema is fixed at build time, generated types are checked by the compiler, and nobody needs to inspect message shape at runtime. It does not fit a gateway that forwards unknown messages, transcodes by descriptor, or runs on a `no_std` target without checking the `tests-no-std` crate first.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • tokio-rs/prost4,798 ★ · +28
Star history
Repository2026-09-232026-11-14
tokio-rs/prost4.8k4.8k

28 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

protoc is a prerequisite you install yourself

The most consequential line in the README is a negative one. From the `prost-build` v0.11 release onwards, `protoc` is required to invoke `compile_protos`, unless `skip_protoc` is enabled, and prost no longer provides a bundled `protoc` or attempts to compile one for you. Install instructions are delegated to the protobuf compiler installation page.

That turns a one dependency install into a two step setup. Your `Cargo.toml` gets the crates:

toml
[dependencies]
prost = "0.14"
# Only necessary if using Protobuf well-known types:
prost-types = "0.14"

and your build environment, local or CI, needs a `protoc` binary on the path before `prost-build` will produce anything. The README recommends `prost-build` as the way to wire `.proto` compilation into a Cargo project, and points at the snazzy repository as a start to finish example, so the shape of the setup is documented, but the compiler itself is now your dependency to manage on every machine that builds the crate.

Enums are stored as i32 with a typed wrapper beside them

Every `.proto` enumeration converts to the Rust `i32` type, and prost also generates a matching Rust `enum`. The reason for the split is the Protobuf specification, which mandates that enumeration values are open: decoding an unrecognized value has to stay possible.

So a message field declared as `PhoneType` becomes a plain `i32` with accessor methods bolted on, and because `type` is a Rust keyword the field needs the raw identifier escape:

rust
pub struct PhoneNumber {
    pub number: String,
    pub r#type: i32, // the `r#` is needed because `type` is a Rust keyword
}

impl PhoneNumber {
    pub fn r#type(&self) -> PhoneType { ... }
    pub fn set_type(&mut self, value: PhoneType) { ... }
}

The generated `#[derive(::prost::Enumeration)]` also adds a `const fn is_valid(value: i32) -> bool`, a deprecated `from_i32`, and an `impl TryFrom<i32>`. The behaviour to remember is the last paragraph of that section: the getter returns the Rust enum's default value when the field holds an invalid `i32`. Unknown enum values survive the round trip on the wire and read back as your default variant.

proto3 scalars lose their presence witness by default

This is the sharpest corner in the type mapping. In `proto3` the default representation for a scalar is plain `T`, and during decoding a missing value is populated by `T::default()`. For a user-defined message field the default is `Option<T>`.

| `.proto` Version | Modifier | Rust Type | | --- | --- | --- | | `proto2` | `optional` | `Option<T>` | | `proto2` | `required` | `T` | | `proto3` | default | `T` for scalar types, `Option<T>` otherwise | | `proto3` | `optional` | `Option<T>` | | `proto2`/`proto3` | `repeated` | `Vec<T>` |

The consequence for a reader is concrete: a `proto3` int64 that was never set decodes as `0`, a bool as `false`, a string as empty, and your code cannot tell that apart from a value the sender deliberately set. The README's answer is to add the `optional` modifier to enforce an `Option<T>` representation for that field. If your service needs to distinguish absent from zero, that modifier has to be in the `.proto`, not added later in Rust.

The protobuf package specifier becomes your module path

prost respects the `package` specifier when it organises generated code into Rust modules. Given a file declaring:

protobuf
package foo.bar;

every type generated from that file lands in the `foo::bar` module. The mapping is one to one, which keeps generated code namespaced, but it also couples your Rust module tree to your schema naming.

The practical risk is a rename. Changing a Protobuf package from `foo.bar` to `foo.baz` renames modules across your crate, and any hand written `use` path or public API that exposed those types breaks. Since generated types are usually part of a public interface between services, a package rename is a breaking change for consumers even though the wire format may be unaffected.

Files with no package specifier at all are also handled; prost generates code for them rather than refusing, so mixing packaged and unpackaged `.proto` files in one build is possible.

The MSRV moves on a six month clock

Minimum supported Rust version is policy here, not an accident. prost keeps a rolling MSRV policy of at least six months, and when the MSRV increases the new Rust version must have been released at least six months earlier. The current MSRV is 1.85, and the workspace agrees: `rust-version = "1.85"` in `[workspace.package]` with `edition = "2021"`.

The rule has two halves and both are worth internalising. You get at least half a year of notice before the floor moves, and you will never be asked to jump onto a Rust release from last week. The cost is that the floor does rise, on a schedule set by the project rather than by you, and a workspace pinned to an older toolchain eventually has to move.

The workspace also carries separate test crates named for editions 2015, 2018 and 2024, plus a `tests-no-std` crate, which is the shape you would expect from a crate that has to keep working across Rust editions and without the standard library.

No runtime reflection and no message descriptors

The list of things prost does differently ends with what it deliberately leaves out: it does not include support for runtime reflection or message descriptors. That is the single most consequential omission for deciding whether it fits.

With descriptors, a program can ask a message what fields it has at runtime, load a schema it was not compiled against, and forward bytes it does not understand. Without them, the generated Rust types are the schema. Everything your binary can do with protobuf is decided at compile time by what the code generator emitted, and there is no path to a generic gateway or a descriptor-driven transcoder on top of prost.

The other items on that list push the same way. Serialization goes through the `bytes::{Buf, BufMut}` abstractions rather than `std::io::{Read, Write}`, which fits async code and avoids an intermediate buffer type. Comments from `.proto` files are retained in the generated Rust, and existing Rust types that were not generated from a `.proto` can be serialized by adding attributes.

One workspace, sixteen crates, two release scripts

prost is not a single crate. The workspace members list covers `prost`, `prost-build`, `prost-derive`, `prost-types`, `protobuf`, plus `benchmarks`, `conformance`, `fuzz` and a run of test crates including `tests`, `tests-2015`, `tests-2018`, `tests-2024`, `tests-no-std`, `tests/prost-path`, `tests/reexported-prost` and `tests/single-include`.

Two of those names answer questions you would otherwise have to guess at. A `conformance` member means the implementation is checked against the Protocol Buffers conformance suite rather than only against its own tests, and `tests-no-std` means the no standard library configuration is a tested target rather than an aspiration.

The supporting files describe how the project is checked and shipped: `FUZZING.md`, `KANI.md`, `clippy.toml`, `taplo.toml`, `flake.nix` with a lock file, and separate `prepare-release.sh` and `publish-release.sh` scripts. The version lives once in `[workspace.package]` at 0.14.4 with the Apache-2.0 licence, so all crates move together.

Editorial conclusion

prost fits a Rust service where the schema is fixed at build time, generated types are checked by the compiler, and nobody needs to inspect message shape at runtime. It does not fit a gateway that forwards unknown messages, transcodes by descriptor, or runs on a `no_std` target without checking the `tests-no-std` crate first. Verify three things before you commit: that protoc exists in your build image, because the crate no longer supplies one; that your Rust toolchain is at or above the MSRV of 1.85 and can absorb the next rise; and that your proto3 fields which need presence tracking carry the `optional` modifier rather than relying on a scalar default.

Frequently asked questions

What does the prost crate generate from a .proto file?

Simple, idiomatic Rust types built on derive attributes: a `message` becomes a struct deriving `Message`, comments from the `.proto` are carried into the generated Rust, and the Protobuf package specifier becomes a Rust module path.

Do I need protoc installed to use prost?

Yes. Since the prost-build v0.11 release, protoc is required to invoke compile_protos unless skip_protoc is enabled, and prost no longer ships a bundled protoc or compiles one for you.

What is the minimum supported Rust version for prost?

The current MSRV is 1.85, and the policy is a rolling window of at least six months: when the MSRV increases, the new Rust version must have been released at least six months earlier. The workspace sets rust-version to 1.85.

Does prost support runtime reflection?

No. Runtime reflection and message descriptors are explicitly out of scope, so the generated Rust types are the schema your program works against.

How do I tell an absent proto3 scalar from its zero value in prost?

In proto3 a scalar field defaults to `T`, and a missing value is populated with `T::default()` during decoding. Add the `optional` modifier to force an `Option<T>` for that field; user-defined message fields already generate as `Option<T>`.

What do I add to Cargo.toml to use prost?

`prost = "0.14"`, plus `prost-types = "0.14"` if you use the Protobuf well-known types. The README recommends the prost-build library for compiling .proto files as part of a Cargo project.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. tokio-rs/prost on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tokio-rs-prost.svg)](https://hysenlabs.com/projects/tokio-rs-prost)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/tokio-rs-prost.svg)](https://hysenlabs.com/projects/tokio-rs-prost)

Was this useful?