Translate and synthesize text with Rust and gRPC

Michael Habib
RustED
Published in
4 min readFeb 25, 2019

gRPC & Protocol Buffers

gRPC is one of the great ways to create RPC services. It was created by Google to be a high performance, salable, and durable RPC framework. In addition to its performance benefits, clients and servers can be written in many different languages. For example, having a server written in Rust does not mean that only Rust clients can communicate with the service. Like other RPC frameworks you define a contract on the different types and services available. In a service definition there are methods and their return and parameter types. Custom message types can also be used to better represent data sent between a server and client. By default, gRPC uses protocol buffers as its Interface Definition Language (IDL) to define this contract. Protocol buffers are a mechanism for serializing structured data. It is like JSON and XML but much faster, smaller, and simpler. For more information on protocol buffers, see here.

Prerequisites

Clone and fork the repo from here .

In this post we will be building a gRPC sever and client written in Rust. One of the methods will be able to translate text and the other will be able to translate and synthesize text to an mp3 file. The client will ask questions and call either translate or synthesize .

Since our server and client implementations will be written in Rust, it should be installed on your system. The gRPC crate that we will be using does have requirements so make sure those are installed as well. You can download the protoc compiler from here. To get started run cargo new translate-service and match the tree structure below.

$ tree
.
├── Cargo.toml
└── src
├── client.rs
├── protos
│ ├── language.proto
│ └── mod.rs
└── server.rs
2 directories, 5files

Make sure the Cargo.toml matches the one shown to ensure all the dependencies are installed.

Server implementation

Our first step will be to create the protocol buffer file to define our service and messages. In language.proto add the following contents.

Like I mentioned earlier, our service will only have two methods. It will also use custom message types to better represent the data being passed. At this point we now need to generate the Rust code from our protocol buffer definitions. In general this code generation process is needed to use and implement the services and messages defined in proto files. Being able to generate these files is a sure way to tell if the language you’re using supports gRPC. In our case we can either let cargo generate them for us with a build script or generate them manually. To do so manually run protoc --rust_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_rust_plugin` language.proto in src/protos .

Using the build script approach will require additional dependencies. More details on setting that up can be found here. The build.rs mentioned in the instructions should look like the following.

Add the following to declare the modules from the generated code.

// two lines don't need their own gist
pub mod language;
pub mod language_grpc;

Now we are finally ready to implement our server. The reason we say “implement” is because in languages like Go and Rust the services are generated into interfaces / traits. I would not be surprised if it was very similar for other languages as well. To do the actual translating and synthesizing we will be using the AWS SDK for Rust(rusoto). Open src/server.rs and add the following contents.

The first important thing to notice is that we define the struct that will be responsible for implementing the Language trait in src/protos/language_grpc.rs . Keep in mind that in both the translate and synthesize methods, we are making synchronous calls to AWS. Using futures in this simple context would not give us any advantages.

Now lets write our client. Open src/client.rs and add the following.

The client is a command line application that asks the user several questions and returns the translated text or audio bytes. It’s worth noting that the client will save the bytes as an mp3 file on your system. If you do not want this to happen comment out the fs::write call.

To test everything out open up two terminal windows or tabs. In one run cargo run --bin server and the other cargo run --bin client . From that point just answer the client applications questions. If everything worked out you should see outputs like the screenshots below.

server.rs

I hope you found this post useful in seeing one of the ways to use gRPC with Rust. Please feel free to comment, ask questions, or suggest a topic I can write about next.

P.S. Follow me on Twitter

--

--