Project Structure
This guide demonstrates how to organize a Rust gRPC server by separating protobuf-generated code into its own module.
project/
├── build.rs # Protobuf code generation
├── Cargo.toml # Rust project configuration
├── proto/
│ └── euclidolap.proto # Protocol buffer definitions
├── src/
│ ├── euclidolap/
│ │ ├── mod.rs # Module entry point
│ │ └── euclidolap.rs # Generated gRPC code
│ └── main.rs # Application entry point
build.rs Configuration
Configure the build script to output generated code to the src/euclidolap/ directory:
fn main() {
let generated_dir = "src/euclidolap";
if let Err(e) = tonic_build::configure()
.out_dir(generated_dir)
.compile(&["proto/euclidolap.proto"], &["proto"])
{
eprintln!("Proto compilation failed: {}", e);
std::process::exit(1);
}
println!("cargo:rerun-if-changed=proto/euclidolap.proto");
}
The out_dir setting directs the generated Rust files into the specified module directory.
Module Entry Point
Create src/euclidolap/mod.rs to expoce the generated code:
pub mod euclidolap {
include!("euclidolap/euclidolap.rs");
}
The include! macro brings the generated code into scope within the module.
Service Implementation
The main application file implements the gRPC service logic:
mod euclidolap;
use euclidolap::euclidolap::olap_service_server::{OLAPService, OLAPServiceServer};
use euclidolap::euclidolap::{OLAPRequest, OLAPResponse, Row};
use tonic::{transport::Server, Request, Response, Status};
#[derive(Debug, Default)]
pub struct AnalysisService {}
#[tonic::async_trait]
impl OLAPService for AnalysisService {
async fn execute_operation(
&self,
request: Request<OLAPRequest>,
) -> Result<Response<OLAPResponse>, Status> {
let payload = request.into_inner();
println!("Request received - Type: {}, Query: {}",
payload.operation_type,
payload.statement);
let result = OLAPResponse {
rows: vec![Row {
columns: vec!["Data_A".to_string(), "Data_B".to_string()],
}],
};
Ok(Response::new(result))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listen_addr = "0.0.0.0:50052".parse().unwrap();
let service_instance = AnalysisService::default();
println!(">>> Analysis Server running on {} <<<", listen_addr);
Server::builder()
.add_service(OLAPServiceServer::new(service_instance))
.serve(listen_addr)
.await?;
Ok(())
}
The AnalysisService struct implements the OLAPService trait, handling incoming requests and returning mock responses.
Protocol Buffer Definition
The corresponding .proto file:
syntax = "proto3";
package euclidolap;
service OLAPService {
rpc ExecuteOperation(OLAPRequest) returns (OLAPResponse);
}
message OLAPRequest {
string operation_type = 1;
string statement = 2;
}
message OLAPResponse {
repeated Row rows = 1;
}
message Row {
repeated string columns = 1;
}
Generated Artifacts
Running cargo build produces src/euclidolap/euclidolap.rs containing:
- Rust structs mapping to each protobuf message
- Trait definitions for service implementation (e.g.,
OLAPServiceServer) - Serialization and deserialization implementations
Execution Flow
- Build Phase:
cargo buildtriggersbuild.rs, generating Rust code fromeuclidolap.proto - Server Startup: The application binds to
0.0.0.0:50052and registers the service - Request Handling: Incoming
OLAPRequestmessages are processed byexecute_operation - Response Delivery: The method returns an
OLAPResponsecontaining resultt rows