Tao
Tao

Rust Prost-Build Usage Guide

prost is a popular Protobuf implementation and code generation tool for the Rust language. prost_build is the build-time code generation library of prost, which can generate Rust data structures and trait implementations from .proto files. This article will provide a detailed guide on how to use prost-build.

prost-build is a build dependency package used to compile .proto files in the build script (build.rs) of a Rust project. The most basic usage is to use the prost_build::compile_protos function:

rust

fn main() -> Result<(), Box<dyn std::error::Error>> {
    prost_build::compile_protos(
        &["path/to/foo.proto", "path/to/bar.proto"], 
        &["path/to/include"]
    )?;
    Ok(())
}

This will compile the specified .proto files and generate the corresponding Rust code. The first argument is a list of paths to .proto files, and the second argument is a list of directories containing .proto files, used to resolve imported .proto files.

The generated Rust code will be written to the OUT_DIR directory specified by Cargo. You can include the generated modules in your Rust code using the include! macro.

prost-build provides many configuration options to customize the generated code. These options are set through the prost_build::Config struct.

The btree_map method instructs prost-build to generate BTreeMap types for specified fields, instead of the default HashMap. This is useful in no-std environments.

rust

let mut config = prost_build::Config::new();
config.btree_map(&["."]);  // Use BTreeMap for all map fields
config.btree_map(&[".my_package.MyMessage.my_field"]); // Use for specified field only

The bytes method instructs prost-build to generate bytes::Bytes types for specified byte fields, instead of the default Vec<u8>.

rust

config.bytes(&["."]);  // Use Bytes for all bytes fields

prost-build allows you to add custom attributes to the generated types and fields. This is useful for implementing trait derivations or integrating with other crates like serde.

  • field_attribute adds attributes to specified fields.
  • type_attribute adds attributes to specified messages, enums, and oneofs.
  • message_attribute adds attributes to specified messages.
  • enum_attribute adds attributes to specified enums and oneofs.

rust

config.field_attribute(".my_package.MyMessage.id", "#[serde(rename = \"id\")]");
config.type_attribute(".my_package.MyMessage", "#[derive(Serialize, Deserialize)]");

The boxed method instructs prost-build to generate Box<T> types for specified fields.

rust

config.boxed(".my_package.MyMessage.large_field");

You can customize the way prost-build generates gRPC server and client code using the service_generator method.

rust

config.service_generator(Box::new(MyServiceGenerator));

prost-build provides several other options:

  • compile_well_known_types: Generate Protobuf’s well-known types from .proto files instead of using the prost_types crate.
  • disable_comments: Disable generating documentation comments.
  • skip_debug: Skip implementing the Debug trait for specified types.
  • extern_path: Declare externally provided Protobuf packages or types for referencing prost types from other crates.
  • file_descriptor_set_path: Write the FileDescriptorSet to the specified path, which can be used for implementing reflection and other features.
  • skip_protoc_run: In combination with file_descriptor_set_path, generate code from the provided FileDescriptorSet instead of invoking protoc.
  • retain_enum_prefix: Prevent prost from stripping the enum name prefix from enum variant names.
  • out_dir: Specify the output directory for the generated Rust files.
  • default_package_filename: Set the filename for Protobuf without package definitions.
  • enable_type_names: Implement the Name trait for message types to support encoding as Any types.
  • type_name_domain: Specify the domain name prefix for message type URLs.
  • prost_path: Set the path for deriving the Message trait.
  • protoc_arg: Add command-line arguments for protoc.
  • include_file: Generate a module file that includes all generated files for simplified inclusion.
  • format: Control whether to format the generated code using prettyplease.

Here is a complete build.rs example showcasing multiple configuration options in prost-build:

rust

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = prost_build::Config::new();

    // Generate BTreeMap for all map fields
    config.btree_map(&["."]);
    
    // Generate bytes::Bytes for bytes field
    config.bytes(&[".my_package.MyMessage.data"]);
    
    // Add derived Serialize/Deserialize
    config.type_attribute(".my_package.MyMessage", "#[derive(Serialize, Deserialize)]");
    
    // Customize serialized field name
    config.field_attribute(".my_package.MyMessage.my_field", "#[serde(rename = \"myField\")]");
    
    // Generate Box for large field
    config.boxed(".my_package.MyMessage.large_data");
    
    // Skip generating debug implementation
    config.skip_debug(&["."]);

    config.compile_protos(&["proto/my_package.proto"], &["proto"])?;

    Ok(())
}

In this example, we:

  1. Generate BTreeMap for all map fields
  2. Generate bytes::Bytes for the data field of MyMessage
  3. Derive Serialize and Deserialize traits for MyMessage
  4. Customize the serialized field name for my_field to myField
  5. Generate Box type for the large_data field
  6. Skip generating Debug implementation for all types
  7. Compile proto/my_package.proto, with include directory proto

By configuring these options, we can fully customize the Protobuf code generation results.

prost-build provides powerful and flexible capabilities for integrating Protobuf with Rust. By understanding its configuration options in depth, we can generate customized Rust code tailored to our project’s needs. Whether it’s simple message types or complex gRPC services, prost-build can meet your requirements.