xref: /tonic/examples/routeguide-tutorial.md (revision f07abb27)
1d9a481baSLucio Franco# gRPC Basics: Tonic
2d9a481baSLucio Franco
3d9a481baSLucio FrancoThis tutorial, adapted from [grpc-go], provides a basic introduction to working with gRPC
4d9a481baSLucio Francoand Tonic. By walking through this example you'll learn how to:
5d9a481baSLucio Franco
6d9a481baSLucio Franco- Define a service in a `.proto` file.
7d9a481baSLucio Franco- Generate server and client code.
8d9a481baSLucio Franco- Write a simple client and server for your service.
9d9a481baSLucio Franco
10d9a481baSLucio FrancoIt assumes you are familiar with [protocol buffers] and basic Rust. Note that the example in
11d9a481baSLucio Francothis tutorial uses the proto3 version of the protocol buffers language, you can find out more in the
12d9a481baSLucio Franco[proto3 language guide][proto3].
13d9a481baSLucio Franco
14d9a481baSLucio Franco[grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md
15d9a481baSLucio Franco[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview
16d9a481baSLucio Franco[proto3]: https://developers.google.com/protocol-buffers/docs/proto3
17d9a481baSLucio Franco
18d9a481baSLucio Franco## Why use gRPC?
19d9a481baSLucio Franco
20d9a481baSLucio FrancoOur example is a simple route mapping application that lets clients get information about features
21d9a481baSLucio Francoon their route, create a summary of their route, and exchange route information such as traffic
22d9a481baSLucio Francoupdates with the server and other clients.
23d9a481baSLucio Franco
24d9a481baSLucio FrancoWith gRPC we can define our service once in a `.proto` file and implement clients and servers in
25d9a481baSLucio Francoany of gRPC's supported languages, which in turn can be run in environments ranging from servers
26d9a481baSLucio Francoinside Google to your own tablet - all the complexity of communication between different languages
27d9a481baSLucio Francoand environments is handled for you by gRPC. We also get all the advantages of working with
28d9a481baSLucio Francoprotocol buffers, including efficient serialization, a simple IDL, and easy interface updating.
29d9a481baSLucio Franco
30d9a481baSLucio Franco## Prerequisites
31d9a481baSLucio Franco
32d9a481baSLucio FrancoTo run the sample code and walk through the tutorial, the only prerequisite is Rust itself.
33d9a481baSLucio Franco[rustup] is a convenient tool to install it, if you haven't already.
34d9a481baSLucio Franco
35d9a481baSLucio Franco[rustup]: https://rustup.rs
36d9a481baSLucio Franco
37d9a481baSLucio Franco## Running the example
38d9a481baSLucio Franco
39d9a481baSLucio FrancoClone or download Tonic's repository:
40d9a481baSLucio Franco
41d9a481baSLucio Franco```shell
42d9a481baSLucio Franco$ git clone https://github.com/hyperium/tonic.git
43d9a481baSLucio Franco```
44d9a481baSLucio Franco
45d9a481baSLucio FrancoChange your current directory to Tonic's repository root:
46d9a481baSLucio Franco```shell
47d9a481baSLucio Franco$ cd tonic
48d9a481baSLucio Franco```
49d9a481baSLucio Franco
50d9a481baSLucio FrancoRun the server
51d9a481baSLucio Franco```shell
52d9a481baSLucio Franco$ cargo run --bin routeguide-server
53d9a481baSLucio Franco```
54d9a481baSLucio Franco
55d9a481baSLucio FrancoIn a separate shell, run the client
56d9a481baSLucio Franco```shell
57d9a481baSLucio Franco$ cargo run --bin routeguide-client
58d9a481baSLucio Franco```
59d9a481baSLucio Franco
60d9a481baSLucio FrancoYou should see some logging output flying past really quickly on both terminal windows. On the
61d9a481baSLucio Francoshell where you ran the client binary, you should see the output of the bidirectional streaming rpc,
62d9a481baSLucio Francoprinting 1 line per second:
63d9a481baSLucio Franco
64faa26ac1SJuan Alvarez```
65d9a481baSLucio FrancoNOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" }
66faa26ac1SJuan Alvarez```
67d9a481baSLucio Franco
68d9a481baSLucio FrancoIf you scroll up you should see the output of the other 3 request types: simple rpc, server-side
69d9a481baSLucio Francostreaming and client-side streaming.
70d9a481baSLucio Franco
71d9a481baSLucio Franco
72d9a481baSLucio Franco## Project setup
73d9a481baSLucio Franco
74d9a481baSLucio FrancoWe will develop our example from scratch in a new crate:
75d9a481baSLucio Franco
76d9a481baSLucio Franco```shell
77d9a481baSLucio Franco$ cargo new routeguide
78d9a481baSLucio Franco$ cd routeguide
79d9a481baSLucio Franco```
80d9a481baSLucio Franco
81d9a481baSLucio Franco
82d9a481baSLucio Franco## Defining the service
83d9a481baSLucio Franco
84d9a481baSLucio FrancoOur first step is to define the gRPC *service* and the method *request* and *response* types using
85d9a481baSLucio Franco[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root.
86d9a481baSLucio FrancoNote that Tonic does not really care where our `.proto` definitions live. We will see how to use
87d9a481baSLucio Francodifferent [code generation configuration](#tonic-build) later in the tutorial.
88d9a481baSLucio Franco
89d9a481baSLucio Franco```shell
90d9a481baSLucio Franco$ mkdir proto && touch proto/route_guide.proto
91d9a481baSLucio Franco```
92d9a481baSLucio Franco
93d9a481baSLucio FrancoYou can see the complete `.proto` file in
946566b237SFletcher Nichol[examples/proto/routeguide/route_guide.proto][routeguide-proto].
95d9a481baSLucio Franco
96d9a481baSLucio FrancoTo define a service, you specify a named `service` in your `.proto` file:
97d9a481baSLucio Franco
98d9a481baSLucio Franco```proto
99d9a481baSLucio Francoservice RouteGuide {
100d9a481baSLucio Franco   ...
101d9a481baSLucio Franco}
102d9a481baSLucio Franco```
103d9a481baSLucio Franco
104d9a481baSLucio FrancoThen you define `rpc` methods inside your service definition, specifying their request and response
105d9a481baSLucio Francotypes. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide`
106d9a481baSLucio Francoservice:
107d9a481baSLucio Franco
108d9a481baSLucio Franco- A *simple RPC* where the client sends a request to the server and waits for a response to come
109d9a481baSLucio Francoback, just like a normal function call.
110d9a481baSLucio Franco```proto
111d9a481baSLucio Franco   // Obtains the feature at a given position.
112d9a481baSLucio Franco   rpc GetFeature(Point) returns (Feature) {}
113d9a481baSLucio Franco```
114d9a481baSLucio Franco
115d9a481baSLucio Franco- A *server-side streaming RPC* where the client sends a request to the server and gets a stream
116d9a481baSLucio Francoto read a sequence of messages back. The client reads from the returned stream until there are
117d9a481baSLucio Francono more messages. As you can see in our example, you specify a server-side streaming method by
118d9a481baSLucio Francoplacing the `stream` keyword before the *response* type.
119d9a481baSLucio Franco```proto
120d9a481baSLucio Franco  // Obtains the Features available within the given Rectangle.  Results are
121d9a481baSLucio Franco  // streamed rather than returned at once (e.g. in a response message with a
122d9a481baSLucio Franco  // repeated field), as the rectangle may cover a large area and contain a
123d9a481baSLucio Franco  // huge number of features.
124d9a481baSLucio Franco  rpc ListFeatures(Rectangle) returns (stream Feature) {}
125d9a481baSLucio Franco```
126d9a481baSLucio Franco
127d9a481baSLucio Franco- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to
128d9a481baSLucio Francothe server. Once the client has finished writing the messages, it waits for the server to read them
129d9a481baSLucio Francoall and return its response. You specify a client-side streaming method by placing the `stream`
130d9a481baSLucio Francokeyword before the *request* type.
131d9a481baSLucio Franco```proto
132d9a481baSLucio Franco  // Accepts a stream of Points on a route being traversed, returning a
133d9a481baSLucio Franco  // RouteSummary when traversal is completed.
134d9a481baSLucio Franco  rpc RecordRoute(stream Point) returns (RouteSummary) {}
135d9a481baSLucio Franco```
136d9a481baSLucio Franco
137d9a481baSLucio Franco- A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams
138d9a481baSLucio Francooperate independently, so clients and servers can read and write in whatever
139d9a481baSLucio Francoorder they like: for example, the server could wait to receive all the client messages before
140d9a481baSLucio Francowriting its responses, or it could alternately read a message then write a message, or some other
141d9a481baSLucio Francocombination of reads and writes. The order of messages in each stream is preserved. You specify
142d9a481baSLucio Francothis type of method by placing the `stream` keyword before both the request and the response.
143d9a481baSLucio Franco```proto
144d9a481baSLucio Franco  // Accepts a stream of RouteNotes sent while a route is being traversed,
145d9a481baSLucio Franco  // while receiving other RouteNotes (e.g. from other users).
146d9a481baSLucio Franco  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
147d9a481baSLucio Franco```
148d9a481baSLucio Franco
149d9a481baSLucio FrancoOur `.proto` file also contains protocol buffer message type definitions for all the request and
150d9a481baSLucio Francoresponse types used in our service methods - for example, here's the `Point` message type:
151d9a481baSLucio Franco```proto
152d9a481baSLucio Franco// Points are represented as latitude-longitude pairs in the E7 representation
153d9a481baSLucio Franco// (degrees multiplied by 10**7 and rounded to the nearest integer).
154d9a481baSLucio Franco// Latitudes should be in the range +/- 90 degrees and longitude should be in
155d9a481baSLucio Franco// the range +/- 180 degrees (inclusive).
156d9a481baSLucio Francomessage Point {
157d9a481baSLucio Franco  int32 latitude = 1;
158d9a481baSLucio Franco  int32 longitude = 2;
159d9a481baSLucio Franco}
160d9a481baSLucio Franco```
161d9a481baSLucio Franco
1626566b237SFletcher Nichol[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/examples/proto/routeguide/route_guide.proto
163d9a481baSLucio Franco
164d9a481baSLucio Franco## Generating client and server code
165d9a481baSLucio Franco
166d9a481baSLucio FrancoTonic can be configured to generate code as part cargo's normal build process. This is very
167d9a481baSLucio Francoconvenient because once we've set everything up, there is no extra step to keep the generated code
168d9a481baSLucio Francoand our `.proto` definitions in sync.
169d9a481baSLucio Franco
170d9a481baSLucio FrancoBehind the scenes, Tonic uses [PROST!] to handle protocol buffer serialization and code
171d9a481baSLucio Francogeneration.
172d9a481baSLucio Franco
173d9a481baSLucio FrancoEdit `Cargo.toml` and add all the dependencies we'll need for this example:
174d9a481baSLucio Franco
175d9a481baSLucio Franco```toml
176d9a481baSLucio Franco[dependencies]
177eb66b914SEaswar Swaminathantonic = "*"
1784aad5afaSLukeprost = "0.13"
179cb9ed185SDaniel Mangumtokio = { version = "1.0", features = ["rt-multi-thread", "macros", "sync", "time"] }
180db22c356SJunichi Sugiuratokio-stream = "0.1"
1814e5c6c8fSJuan Alvarez
1824e5c6c8fSJuan Alvarezasync-stream = "0.2"
183d9a481baSLucio Francoserde = { version = "1.0", features = ["derive"] }
184d9a481baSLucio Francoserde_json = "1.0"
18537bef0b2Stsingwongrand = "0.8"
186d9a481baSLucio Franco
187d9a481baSLucio Franco[build-dependencies]
188eb66b914SEaswar Swaminathantonic-build = "*"
189d9a481baSLucio Franco```
190d9a481baSLucio Franco
191d9a481baSLucio FrancoCreate a `build.rs` file at the root of your crate:
192d9a481baSLucio Franco
193d9a481baSLucio Franco```rust
194d9a481baSLucio Francofn main() {
1959e7d35a2SGovardhan G D    tonic_build::compile_protos("proto/route_guide.proto")
196d9a481baSLucio Franco        .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
197d9a481baSLucio Franco}
198d9a481baSLucio Franco```
199d9a481baSLucio Franco
200d9a481baSLucio Franco```shell
201d9a481baSLucio Franco$ cargo build
202d9a481baSLucio Franco```
203d9a481baSLucio Franco
204d9a481baSLucio FrancoThat's it. The generated code contains:
205d9a481baSLucio Franco
206d9a481baSLucio Franco- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`.
2074e5c6c8fSJuan Alvarez- A service trait we'll need to implement: `route_guide_server::RouteGuide`.
2084e5c6c8fSJuan Alvarez- A client type we'll use to call the server: `route_guide_client::RouteGuideClient<T>`.
209d9a481baSLucio Franco
210d9a481baSLucio FrancoIf your are curious as to where the generated files are, keep reading. The mystery will be revealed
211d9a481baSLucio Francosoon! We can now move on to the fun part.
212d9a481baSLucio Franco
213d9a481baSLucio Franco[PROST!]: https://github.com/danburkert/prost
214d9a481baSLucio Franco
215d9a481baSLucio Franco## Creating the server
216d9a481baSLucio Franco
217d9a481baSLucio FrancoFirst let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC
218d9a481baSLucio Francoclients, you can skip this section and go straight to [Creating the client](#client)
219d9a481baSLucio Franco(though you might find it interesting anyway!).
220d9a481baSLucio Franco
221d9a481baSLucio FrancoThere are two parts to making our `RouteGuide` service do its job:
222d9a481baSLucio Franco
223d9a481baSLucio Franco- Implementing the service trait generated from our service definition.
224d9a481baSLucio Franco- Running a gRPC server to listen for requests from clients.
225d9a481baSLucio Franco
226d9a481baSLucio FrancoYou can find our example `RouteGuide` server in
2276566b237SFletcher Nichol[examples/src/routeguide/server.rs][routeguide-server].
228d9a481baSLucio Franco
2296566b237SFletcher Nichol[routeguide-server]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs
230d9a481baSLucio Franco
2314e5c6c8fSJuan Alvarez### Implementing the RouteGuide server trait
232d9a481baSLucio Franco
233d9a481baSLucio FrancoWe can start by defining a struct to represent our service, we can do this on `main.rs` for now:
234d9a481baSLucio Franco
235d9a481baSLucio Franco```rust
236d9a481baSLucio Franco#[derive(Debug)]
2374e5c6c8fSJuan Alvarezstruct RouteGuideService;
238d9a481baSLucio Franco```
239d9a481baSLucio Franco
2404e5c6c8fSJuan AlvarezNext, we need to implement the `route_guide_server::RouteGuide` trait that is generated in our build step.
241d9a481baSLucio FrancoThe generated code is placed inside our target directory, in a location defined by the `OUT_DIR`
242d9a481baSLucio Francoenvironment variable that is set by cargo. For our example, this means you can find the generated
243d9a481baSLucio Francocode in a path similar to `target/debug/build/routeguide/out/routeguide.rs`.
244d9a481baSLucio Franco
245d9a481baSLucio FrancoYou can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book].
246d9a481baSLucio Franco
247d9a481baSLucio FrancoWe can use Tonic's `include_proto` macro to bring the generated code into scope:
248d9a481baSLucio Franco
249d9a481baSLucio Franco```rust
250d9a481baSLucio Francopub mod routeguide {
251d9a481baSLucio Franco    tonic::include_proto!("routeguide");
252d9a481baSLucio Franco}
253d9a481baSLucio Franco
254faa26ac1SJuan Alvarezuse routeguide::route_guide_server::{RouteGuide, RouteGuideServer};
2554e5c6c8fSJuan Alvarezuse routeguide::{Feature, Point, Rectangle, RouteNote, RouteSummary};
256d9a481baSLucio Franco```
257d9a481baSLucio Franco
258d9a481baSLucio Franco**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of
259ddab65edSJerboas86the package declared in our `.proto` file, not a filename, e.g "routeguide.rs".
260d9a481baSLucio Franco
261d9a481baSLucio FrancoWith this in place, we can stub out our service implementation:
262d9a481baSLucio Franco
263d9a481baSLucio Franco```rust
264faa26ac1SJuan Alvarezuse std::pin::Pin;
265faa26ac1SJuan Alvarezuse std::sync::Arc;
2664e5c6c8fSJuan Alvarezuse tokio::sync::mpsc;
2674e5c6c8fSJuan Alvarezuse tonic::{Request, Response, Status};
26819e5f396Stottotouse tokio_stream::{wrappers::ReceiverStream, Stream};
269d9a481baSLucio Franco```
270d9a481baSLucio Franco
271d9a481baSLucio Franco```rust
272d9a481baSLucio Franco#[tonic::async_trait]
273faa26ac1SJuan Alvarezimpl RouteGuide for RouteGuideService {
274d9a481baSLucio Franco    async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> {
275d9a481baSLucio Franco        unimplemented!()
276d9a481baSLucio Franco    }
277d9a481baSLucio Franco
278db22c356SJunichi Sugiura    type ListFeaturesStream = ReceiverStream<Result<Feature, Status>>;
279d9a481baSLucio Franco
280d9a481baSLucio Franco    async fn list_features(
281d9a481baSLucio Franco        &self,
282d9a481baSLucio Franco        _request: Request<Rectangle>,
283d9a481baSLucio Franco    ) -> Result<Response<Self::ListFeaturesStream>, Status> {
284d9a481baSLucio Franco        unimplemented!()
285d9a481baSLucio Franco    }
286d9a481baSLucio Franco
287d9a481baSLucio Franco    async fn record_route(
288d9a481baSLucio Franco        &self,
289d9a481baSLucio Franco        _request: Request<tonic::Streaming<Point>>,
290d9a481baSLucio Franco    ) -> Result<Response<RouteSummary>, Status> {
291d9a481baSLucio Franco        unimplemented!()
292d9a481baSLucio Franco    }
293d9a481baSLucio Franco
29423c1392fSLucio Franco    type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send  + 'static>>;
295d9a481baSLucio Franco
296d9a481baSLucio Franco    async fn route_chat(
297d9a481baSLucio Franco        &self,
298d9a481baSLucio Franco        _request: Request<tonic::Streaming<RouteNote>>,
299d9a481baSLucio Franco    ) -> Result<Response<Self::RouteChatStream>, Status> {
300d9a481baSLucio Franco        unimplemented!()
301d9a481baSLucio Franco    }
302d9a481baSLucio Franco}
303d9a481baSLucio Franco```
304d9a481baSLucio Franco
305d9a481baSLucio Franco**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It
306d9a481baSLucio Francouses [async-trait] internally. You can learn more about `async fn` in traits in the [async book].
307d9a481baSLucio Franco
308d9a481baSLucio Franco
309d9a481baSLucio Franco[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
310d9a481baSLucio Franco[async-trait]: https://github.com/dtolnay/async-trait
3113c2a4a28SMichael-J-Ward[async book]: https://rust-lang.github.io/async-book/07_workarounds/05_async_in_traits.html
312d9a481baSLucio Franco
313d9a481baSLucio Franco### Server state
314d9a481baSLucio FrancoOur service needs access to an immutable list of features. When the server starts, we are going to
315d9a481baSLucio Francodeserialize them from a json file and keep them around as our only piece of shared state:
316d9a481baSLucio Franco
317d9a481baSLucio Franco```rust
318d9a481baSLucio Franco#[derive(Debug)]
3194e5c6c8fSJuan Alvarezpub struct RouteGuideService {
320d9a481baSLucio Franco    features: Arc<Vec<Feature>>,
321d9a481baSLucio Franco}
322d9a481baSLucio Franco```
323d9a481baSLucio Franco
324d9a481baSLucio FrancoCreate the json data file and a helper module to read and deserialize our features.
325d9a481baSLucio Franco
326d9a481baSLucio Franco```shell
327d9a481baSLucio Franco$ mkdir data && touch data/route_guide_db.json
328d9a481baSLucio Franco$ touch src/data.rs
329d9a481baSLucio Franco```
330d9a481baSLucio Franco
3316566b237SFletcher NicholYou can find our example json data in [examples/data/route_guide_db.json][route-guide-db] and
332d9a481baSLucio Francothe corresponding `data` module to load and deserialize it in
3336566b237SFletcher Nichol[examples/routeguide/data.rs][data-module].
334d9a481baSLucio Franco
335d9a481baSLucio Franco**Note:** If you are following along, you'll need to change the data file's path  from
3366566b237SFletcher Nichol`examples/data/route_guide_db.json` to `data/route_guide_db.json`.
337d9a481baSLucio Franco
338d9a481baSLucio FrancoNext, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys:
339d9a481baSLucio Franco
340d9a481baSLucio Franco```rust
341d9a481baSLucio Francouse std::hash::{Hasher, Hash};
342d9a481baSLucio Franco```
343d9a481baSLucio Franco
344d9a481baSLucio Franco```rust
345d9a481baSLucio Francoimpl Hash for Point {
346d9a481baSLucio Franco    fn hash<H>(&self, state: &mut H)
347d9a481baSLucio Franco    where
348d9a481baSLucio Franco        H: Hasher,
349d9a481baSLucio Franco    {
350d9a481baSLucio Franco        self.latitude.hash(state);
351d9a481baSLucio Franco        self.longitude.hash(state);
352d9a481baSLucio Franco    }
353d9a481baSLucio Franco}
354d9a481baSLucio Franco
355d9a481baSLucio Francoimpl Eq for Point {}
356d9a481baSLucio Franco
357d9a481baSLucio Franco```
358d9a481baSLucio Franco
359d9a481baSLucio FrancoLastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them
360d9a481baSLucio Francowhen performing feature lookups. You can find them in
3616566b237SFletcher Nichol[examples/src/routeguide/server.rs][in-range-fn].
362d9a481baSLucio Franco
3636566b237SFletcher Nichol[route-guide-db]: https://github.com/hyperium/tonic/blob/master/examples/data/route_guide_db.json
3646566b237SFletcher Nichol[data-module]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/data.rs
365faa26ac1SJuan Alvarez[in-range-fn]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/server.rs#L174
366d9a481baSLucio Franco
367d9a481baSLucio Franco#### Request and Response types
368d9a481baSLucio FrancoAll our service methods receive a `tonic::Request<T>` and return a
369d9a481baSLucio Franco`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods
370d9a481baSLucio Francoare declared in our *service* `.proto` definition. It can be either:
371d9a481baSLucio Franco
372d9a481baSLucio Franco- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field.
373d9a481baSLucio Franco- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`.
374d9a481baSLucio Franco
375d9a481baSLucio Franco#### Simple RPC
376d9a481baSLucio FrancoLet's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>`
377d9a481baSLucio Francofrom the client and tries to find a feature at the given `Point`. If no feature is found, it returns
378d9a481baSLucio Francoan empty one.
379d9a481baSLucio Franco
380d9a481baSLucio Franco```rust
381d9a481baSLucio Francoasync fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
382d9a481baSLucio Franco    for feature in &self.features[..] {
383d9a481baSLucio Franco        if feature.location.as_ref() == Some(request.get_ref()) {
384d9a481baSLucio Franco            return Ok(Response::new(feature.clone()));
385d9a481baSLucio Franco        }
386d9a481baSLucio Franco    }
387d9a481baSLucio Franco
3884e5c6c8fSJuan Alvarez    Ok(Response::new(Feature::default()))
389d9a481baSLucio Franco}
390d9a481baSLucio Franco```
391d9a481baSLucio Franco
392d9a481baSLucio Franco
393d9a481baSLucio Franco#### Server-side streaming RPC
394d9a481baSLucio FrancoNow let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we
395d9a481baSLucio Franconeed to send back multiple `Feature`s to our client.
396d9a481baSLucio Franco
397d9a481baSLucio Franco```rust
398db22c356SJunichi Sugiuratype ListFeaturesStream = ReceiverStream<Result<Feature, Status>>;
399d9a481baSLucio Franco
400d9a481baSLucio Francoasync fn list_features(
401d9a481baSLucio Franco    &self,
402d9a481baSLucio Franco    request: Request<Rectangle>,
403d9a481baSLucio Franco) -> Result<Response<Self::ListFeaturesStream>, Status> {
4041442a59dSArjan Singh Bal    let (tx, rx) = mpsc::channel(4);
405d9a481baSLucio Franco    let features = self.features.clone();
406d9a481baSLucio Franco
407d9a481baSLucio Franco    tokio::spawn(async move {
408d9a481baSLucio Franco        for feature in &features[..] {
409d9a481baSLucio Franco            if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
410d9a481baSLucio Franco                tx.send(Ok(feature.clone())).await.unwrap();
411d9a481baSLucio Franco            }
412d9a481baSLucio Franco        }
413d9a481baSLucio Franco    });
414d9a481baSLucio Franco
415db22c356SJunichi Sugiura    Ok(Response::new(ReceiverStream::new(rx)))
416d9a481baSLucio Franco}
417d9a481baSLucio Franco```
418d9a481baSLucio Franco
419d9a481baSLucio FrancoLike `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this
420d9a481baSLucio Francocase. This time, however, we need to return a stream of values, rather than a single one.
421d9a481baSLucio FrancoWe create a channel and spawn a new asynchronous task where we perform a lookup, sending
422d9a481baSLucio Francothe features that satisfy our constraints into the channel.
423d9a481baSLucio Franco
424d9a481baSLucio FrancoThe `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`.
425d9a481baSLucio Franco
426d9a481baSLucio Franco
427d9a481baSLucio Franco#### Client-side streaming RPC
428d9a481baSLucio FrancoNow let's look at something a little more complicated: the client-side streaming method
429d9a481baSLucio Franco`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary`
430d9a481baSLucio Francowith information about their trip. As you can see, this time the method receives a
431d9a481baSLucio Franco`tonic::Request<tonic::Streaming<Point>>`.
432d9a481baSLucio Franco
433d9a481baSLucio Franco```rust
434d9a481baSLucio Francouse std::time::Instant;
43519e5f396Stottotouse tokio_stream::StreamExt;
436d9a481baSLucio Franco```
437d9a481baSLucio Franco
438d9a481baSLucio Franco```rust
439d9a481baSLucio Francoasync fn record_route(
440d9a481baSLucio Franco    &self,
441d9a481baSLucio Franco    request: Request<tonic::Streaming<Point>>,
442d9a481baSLucio Franco) -> Result<Response<RouteSummary>, Status> {
44334865a9fSDizans    let mut stream = request.into_inner();
444d9a481baSLucio Franco
445d9a481baSLucio Franco    let mut summary = RouteSummary::default();
446d9a481baSLucio Franco    let mut last_point = None;
447d9a481baSLucio Franco    let now = Instant::now();
448d9a481baSLucio Franco
449d9a481baSLucio Franco    while let Some(point) = stream.next().await {
450d9a481baSLucio Franco        let point = point?;
451d9a481baSLucio Franco        summary.point_count += 1;
452d9a481baSLucio Franco
453d9a481baSLucio Franco        for feature in &self.features[..] {
454d9a481baSLucio Franco            if feature.location.as_ref() == Some(&point) {
455d9a481baSLucio Franco                summary.feature_count += 1;
456d9a481baSLucio Franco            }
457d9a481baSLucio Franco        }
458d9a481baSLucio Franco
459d9a481baSLucio Franco        if let Some(ref last_point) = last_point {
460d9a481baSLucio Franco            summary.distance += calc_distance(last_point, &point);
461d9a481baSLucio Franco        }
462d9a481baSLucio Franco
463d9a481baSLucio Franco        last_point = Some(point);
464d9a481baSLucio Franco    }
465d9a481baSLucio Franco
466d9a481baSLucio Franco    summary.elapsed_time = now.elapsed().as_secs() as i32;
467d9a481baSLucio Franco
468d9a481baSLucio Franco    Ok(Response::new(summary))
469d9a481baSLucio Franco}
470d9a481baSLucio Franco```
471d9a481baSLucio Franco
472d9a481baSLucio Franco`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`.
473d9a481baSLucio FrancoIn other words, we build a summary value as we process each `Point` in our stream, one by one.
474d9a481baSLucio FrancoWhen there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a
475d9a481baSLucio Franco`tonic::Response`.
476d9a481baSLucio Franco
477d9a481baSLucio Franco#### Bidirectional streaming RPC
478d9a481baSLucio FrancoFinally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream
479d9a481baSLucio Francoof `RouteNote`s and returns  a stream of `RouteNote`s.
480d9a481baSLucio Franco
481d9a481baSLucio Franco```rust
482faa26ac1SJuan Alvarezuse std::collections::HashMap;
483faa26ac1SJuan Alvarez```
484faa26ac1SJuan Alvarez
485faa26ac1SJuan Alvarez```rust
486faa26ac1SJuan Alvareztype RouteChatStream =
48723c1392fSLucio Franco    Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send  + 'static>>;
488faa26ac1SJuan Alvarez
489d9a481baSLucio Franco
490d9a481baSLucio Francoasync fn route_chat(
491d9a481baSLucio Franco    &self,
492d9a481baSLucio Franco    request: Request<tonic::Streaming<RouteNote>>,
493d9a481baSLucio Franco) -> Result<Response<Self::RouteChatStream>, Status> {
494d9a481baSLucio Franco    let mut notes = HashMap::new();
49534865a9fSDizans    let mut stream = request.into_inner();
496d9a481baSLucio Franco
497d9a481baSLucio Franco    let output = async_stream::try_stream! {
498d9a481baSLucio Franco        while let Some(note) = stream.next().await {
499d9a481baSLucio Franco            let note = note?;
500d9a481baSLucio Franco
501b2e56654Stottoto            let location = note.location.unwrap();
502d9a481baSLucio Franco
503d9a481baSLucio Franco            let location_notes = notes.entry(location).or_insert(vec![]);
504d9a481baSLucio Franco            location_notes.push(note);
505d9a481baSLucio Franco
506d9a481baSLucio Franco            for note in location_notes {
507d9a481baSLucio Franco                yield note.clone();
508d9a481baSLucio Franco            }
509d9a481baSLucio Franco        }
510d9a481baSLucio Franco    };
511d9a481baSLucio Franco
512d9a481baSLucio Franco    Ok(Response::new(Box::pin(output)
5139c954e09STunahan Karlıbaş        as Self::RouteChatStream))
514d9a481baSLucio Franco
515d9a481baSLucio Franco}
516d9a481baSLucio Franco```
517d9a481baSLucio Franco
518d9a481baSLucio Franco`route_chat` uses the [async-stream] crate to perform an asynchronous transformation
519d9a481baSLucio Francofrom one (input) stream to another (output) stream. As the input is processed, each value is
520d9a481baSLucio Francoinserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream
521d9a481baSLucio Francois then returned to the caller. Neat.
522d9a481baSLucio Franco
523d9a481baSLucio Franco**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected
524d9a481baSLucio Francoto be fixed soon.
525d9a481baSLucio Franco
526d9a481baSLucio Franco[async-stream]: https://github.com/tokio-rs/async-stream
527d9a481baSLucio Franco
528d9a481baSLucio Franco### Starting the server
529d9a481baSLucio Franco
530d9a481baSLucio FrancoOnce we've implemented all our methods, we also need to start up a gRPC server so that clients can
531d9a481baSLucio Francoactually use our service. This is how our `main` function looks like:
532d9a481baSLucio Franco
533d9a481baSLucio Franco```rust
534d9a481baSLucio Francomod data;
535d9a481baSLucio Francouse tonic::transport::Server;
536d9a481baSLucio Franco```
537d9a481baSLucio Franco
538d9a481baSLucio Franco```rust
539d9a481baSLucio Franco#[tokio::main]
540d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> {
541d9a481baSLucio Franco    let addr = "[::1]:10000".parse().unwrap();
542d9a481baSLucio Franco
543faa26ac1SJuan Alvarez    let route_guide = RouteGuideService {
544d9a481baSLucio Franco        features: Arc::new(data::load()),
545d9a481baSLucio Franco    };
546d9a481baSLucio Franco
5474e5c6c8fSJuan Alvarez    let svc = RouteGuideServer::new(route_guide);
548d9a481baSLucio Franco
5494e5c6c8fSJuan Alvarez    Server::builder().add_service(svc).serve(addr).await?;
550d9a481baSLucio Franco
551d9a481baSLucio Franco    Ok(())
552d9a481baSLucio Franco}
553d9a481baSLucio Franco```
554d9a481baSLucio Franco
555d9a481baSLucio FrancoTo handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means,
556d9a481baSLucio Francoamong other things, is that we have a flexible and composable stack we can build on top of. We can,
557faa26ac1SJuan Alvarezfor example, add an [interceptor][authentication-example] to process requests before they reach our service
558faa26ac1SJuan Alvarezmethods.
559d9a481baSLucio Franco
560d9a481baSLucio Franco
561d9a481baSLucio Franco[Tower]: https://github.com/tower-rs
562d9a481baSLucio Franco[hyper]: https://github.com/hyperium/hyper
5636566b237SFletcher Nichol[authentication-example]: https://github.com/hyperium/tonic/blob/master/examples/src/authentication/server.rs#L56
564d9a481baSLucio Franco
565d9a481baSLucio Franco<a name="client"></a>
566d9a481baSLucio Franco## Creating the client
567d9a481baSLucio Franco
568d9a481baSLucio FrancoIn this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our
5696566b237SFletcher Nicholcomplete example client code in [examples/src/routeguide/client.rs][routeguide-client].
570d9a481baSLucio Franco
571d9a481baSLucio FrancoOur crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to
572d9a481baSLucio Francoedit our `Cargo.toml` accordingly:
573d9a481baSLucio Franco
574d9a481baSLucio Franco```toml
575d9a481baSLucio Franco[[bin]]
576d9a481baSLucio Franconame = "routeguide-server"
577d9a481baSLucio Francopath = "src/server.rs"
578d9a481baSLucio Franco
579d9a481baSLucio Franco[[bin]]
580d9a481baSLucio Franconame = "routeguide-client"
581d9a481baSLucio Francopath = "src/client.rs"
582d9a481baSLucio Franco```
583d9a481baSLucio Franco
584d9a481baSLucio FrancoRename `main.rs` to `server.rs` and create a new file `client.rs`.
585d9a481baSLucio Franco
586d9a481baSLucio Franco```shell
587d9a481baSLucio Franco$ mv src/main.rs src/server.rs
588d9a481baSLucio Franco$ touch src/client.rs
589d9a481baSLucio Franco```
590d9a481baSLucio Franco
591faa26ac1SJuan AlvarezTo call service methods, we first need to create a gRPC *client* to communicate with the server. Like in the server
592faa26ac1SJuan Alvarezcase, we'll start by bringing the generated code into scope:
593d9a481baSLucio Franco
594d9a481baSLucio Franco```rust
5954e5c6c8fSJuan Alvarezpub mod routeguide {
596d9a481baSLucio Franco    tonic::include_proto!("routeguide");
597d9a481baSLucio Franco}
598d9a481baSLucio Franco
5994e5c6c8fSJuan Alvarezuse routeguide::route_guide_client::RouteGuideClient;
6004e5c6c8fSJuan Alvarezuse routeguide::{Point, Rectangle, RouteNote};
6014e5c6c8fSJuan Alvarez
602d9a481baSLucio Franco
603d9a481baSLucio Franco#[tokio::main]
604d9a481baSLucio Francoasync fn main() -> Result<(), Box<dyn std::error::Error>> {
605d9a481baSLucio Franco    let mut client = RouteGuideClient::connect("http://[::1]:10000").await?;
606d9a481baSLucio Franco
607d9a481baSLucio Franco     Ok(())
608d9a481baSLucio Franco}
609d9a481baSLucio Franco```
610d9a481baSLucio Franco
611d9a481baSLucio FrancoSame as in the server implementation, we start by bringing our generated code into scope. We then
612d9a481baSLucio Francocreate a client in our main function, passing the server's full URL to `RouteGuideClient::connect`.
613d9a481baSLucio FrancoOur client is now ready to make service calls. Note that `client` is mutable, this is because it
614d9a481baSLucio Franconeeds to manage internal state.
615d9a481baSLucio Franco
6166566b237SFletcher Nichol[routeguide-client]: https://github.com/hyperium/tonic/blob/master/examples/src/routeguide/client.rs
617d9a481baSLucio Franco
618d9a481baSLucio Franco
619d9a481baSLucio Franco### Calling service methods
620d9a481baSLucio FrancoNow let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous,
621d9a481baSLucio Francowhich means that RPC calls need to be `.await`ed.
622d9a481baSLucio Franco
623d9a481baSLucio Franco#### Simple RPC
624d9a481baSLucio FrancoCalling the simple RPC `get_feature` is as straightforward as calling a local method:
625d9a481baSLucio Franco
626d9a481baSLucio Franco```rust
627d9a481baSLucio Francouse tonic::Request;
628d9a481baSLucio Franco```
629d9a481baSLucio Franco
630d9a481baSLucio Franco```rust
631d9a481baSLucio Francolet response = client
632d9a481baSLucio Franco    .get_feature(Request::new(Point {
633d9a481baSLucio Franco        latitude: 409146138,
634d9a481baSLucio Franco        longitude: -746188906,
635d9a481baSLucio Franco    }))
636d9a481baSLucio Franco    .await?;
637d9a481baSLucio Franco
638d9a481baSLucio Francoprintln!("RESPONSE = {:?}", response);
639d9a481baSLucio Franco```
640d9a481baSLucio FrancoWe call the `get_feature` client method, passing a single `Point` value wrapped in a
641d9a481baSLucio Franco`tonic::Request`. We get a `Result<tonic::Response<Feature>, tonic::Status>` back.
642d9a481baSLucio Franco
643d9a481baSLucio Franco#### Server-side streaming RPC
644d9a481baSLucio FrancoHere's where we call the server-side streaming method `list_features`, which returns a stream of
645d9a481baSLucio Francogeographical `Feature`s.
646d9a481baSLucio Franco
647d9a481baSLucio Franco```rust
648d9a481baSLucio Francouse tonic::transport::Channel;
649d9a481baSLucio Francouse std::error::Error;
650d9a481baSLucio Franco```
651d9a481baSLucio Franco
652d9a481baSLucio Franco```rust
653d9a481baSLucio Francoasync fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
654d9a481baSLucio Franco    let rectangle = Rectangle {
655d9a481baSLucio Franco        lo: Some(Point {
656d9a481baSLucio Franco            latitude: 400000000,
657d9a481baSLucio Franco            longitude: -750000000,
658d9a481baSLucio Franco        }),
659d9a481baSLucio Franco        hi: Some(Point {
660d9a481baSLucio Franco            latitude: 420000000,
661d9a481baSLucio Franco            longitude: -730000000,
662d9a481baSLucio Franco        }),
663d9a481baSLucio Franco    };
664d9a481baSLucio Franco
665d9a481baSLucio Franco    let mut stream = client
666d9a481baSLucio Franco        .list_features(Request::new(rectangle))
667d9a481baSLucio Franco        .await?
668d9a481baSLucio Franco        .into_inner();
669d9a481baSLucio Franco
670d9a481baSLucio Franco    while let Some(feature) = stream.message().await? {
6711442a59dSArjan Singh Bal        println!("FEATURE = {:?}", feature);
672d9a481baSLucio Franco    }
673d9a481baSLucio Franco
674d9a481baSLucio Franco    Ok(())
675d9a481baSLucio Franco}
676d9a481baSLucio Franco```
677d9a481baSLucio Franco
678d9a481baSLucio FrancoAs in the simple RPC, we pass a single value request. However, instead of getting a
679d9a481baSLucio Francosingle value back, we get a stream of `Features`.
680d9a481baSLucio Franco
681eeb3268fSpavedroadWe use the `message()` method from the `tonic::Streaming` struct to repeatedly read in the
682d9a481baSLucio Francoserver's responses to a response protocol buffer object (in this case a `Feature`) until there are
683d9a481baSLucio Francono more messages left in the stream.
684d9a481baSLucio Franco
685d9a481baSLucio Franco#### Client-side streaming RPC
686d9a481baSLucio FrancoThe client-side streaming method `record_route` takes a stream of `Point`s and returns a single
687d9a481baSLucio Franco`RouteSummary` value.
688d9a481baSLucio Franco
689d9a481baSLucio Franco```rust
690d9a481baSLucio Francouse rand::rngs::ThreadRng;
691d9a481baSLucio Francouse rand::Rng;
692d9a481baSLucio Franco```
693d9a481baSLucio Franco
694d9a481baSLucio Franco```rust
695d9a481baSLucio Francoasync fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
696*f07abb27Stottoto    let mut rng = rand::rng();
697*f07abb27Stottoto    let point_count: i32 = rng.random_range(2..100);
698d9a481baSLucio Franco
699d9a481baSLucio Franco    let mut points = vec![];
700d9a481baSLucio Franco    for _ in 0..=point_count {
701d9a481baSLucio Franco        points.push(random_point(&mut rng))
702d9a481baSLucio Franco    }
703d9a481baSLucio Franco
704d9a481baSLucio Franco    println!("Traversing {} points", points.len());
70519e5f396Stottoto    let request = Request::new(tokio_stream::iter(points));
706d9a481baSLucio Franco
707d9a481baSLucio Franco    match client.record_route(request).await {
708d9a481baSLucio Franco        Ok(response) => println!("SUMMARY: {:?}", response.into_inner()),
709d9a481baSLucio Franco        Err(e) => println!("something went wrong: {:?}", e),
710d9a481baSLucio Franco    }
711d9a481baSLucio Franco
712d9a481baSLucio Franco    Ok(())
713d9a481baSLucio Franco}
714d9a481baSLucio Franco```
715d9a481baSLucio Franco
716d9a481baSLucio Franco```rust
717d9a481baSLucio Francofn random_point(rng: &mut ThreadRng) -> Point {
718*f07abb27Stottoto    let latitude = (rng.random_range(0..180) - 90) * 10_000_000;
719*f07abb27Stottoto    let longitude = (rng.random_range(0..360) - 180) * 10_000_000;
720d9a481baSLucio Franco    Point {
721d9a481baSLucio Franco        latitude,
722d9a481baSLucio Franco        longitude,
723d9a481baSLucio Franco    }
724d9a481baSLucio Franco}
725d9a481baSLucio Franco```
726d9a481baSLucio Franco
727d9a481baSLucio FrancoWe build a vector of a random number of `Point` values (between 2 and 100) and then convert
72819e5f396Stottotoit into a `Stream` using the `tokio_stream::iter` function. This is a cheap an easy way to get
729d9a481baSLucio Francoa stream suitable for passing into our service method. The resulting stream is then wrapped in a
730d9a481baSLucio Franco`tonic::Request`.
731d9a481baSLucio Franco
732d9a481baSLucio Franco
733d9a481baSLucio Franco#### Bidirectional streaming RPC
734d9a481baSLucio Franco
735d9a481baSLucio FrancoFinally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream
736d9a481baSLucio Francoof `RouteNotes` and returns either another stream of `RouteNotes` or an error.
737d9a481baSLucio Franco
738d9a481baSLucio Franco```rust
7394e5c6c8fSJuan Alvarezuse std::time::Duration;
7404e5c6c8fSJuan Alvarezuse tokio::time;
741d9a481baSLucio Franco```
742d9a481baSLucio Franco
743d9a481baSLucio Franco```rust
744d9a481baSLucio Francoasync fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
7454e5c6c8fSJuan Alvarez    let start = time::Instant::now();
746d9a481baSLucio Franco
747d9a481baSLucio Franco    let outbound = async_stream::stream! {
7484e5c6c8fSJuan Alvarez        let mut interval = time::interval(Duration::from_secs(1));
749d9a481baSLucio Franco
750faa26ac1SJuan Alvarez        while let time = interval.tick().await {
751d9a481baSLucio Franco            let elapsed = time.duration_since(start);
752d9a481baSLucio Franco            let note = RouteNote {
753d9a481baSLucio Franco                location: Some(Point {
754d9a481baSLucio Franco                    latitude: 409146138 + elapsed.as_secs() as i32,
755d9a481baSLucio Franco                    longitude: -746188906,
756d9a481baSLucio Franco                }),
757d9a481baSLucio Franco                message: format!("at {:?}", elapsed),
758d9a481baSLucio Franco            };
759d9a481baSLucio Franco
760d9a481baSLucio Franco            yield note;
761d9a481baSLucio Franco        }
762d9a481baSLucio Franco    };
763d9a481baSLucio Franco
7644e5c6c8fSJuan Alvarez    let response = client.route_chat(Request::new(outbound)).await?;
765d9a481baSLucio Franco    let mut inbound = response.into_inner();
766d9a481baSLucio Franco
767d9a481baSLucio Franco    while let Some(note) = inbound.message().await? {
768d9a481baSLucio Franco        println!("NOTE = {:?}", note);
769d9a481baSLucio Franco    }
770d9a481baSLucio Franco
771d9a481baSLucio Franco    Ok(())
772d9a481baSLucio Franco}
773d9a481baSLucio Franco```
774d9a481baSLucio FrancoIn this case, we use the [async-stream] crate to generate our outbound stream, yielding
775d9a481baSLucio Franco`RouteNote` values in one second intervals. We then iterate over the stream returned by
776d9a481baSLucio Francothe server, printing each value in the stream.
777d9a481baSLucio Franco
778d9a481baSLucio Franco## Try it out!
779d9a481baSLucio Franco
780d9a481baSLucio Franco### Run the server
781d9a481baSLucio Franco```shell
782d9a481baSLucio Franco$ cargo run --bin routeguide-server
783d9a481baSLucio Franco```
784d9a481baSLucio Franco
785d9a481baSLucio Franco### Run the client
786d9a481baSLucio Franco```shell
787d9a481baSLucio Franco$ cargo run --bin routeguide-client
788d9a481baSLucio Franco```
789d9a481baSLucio Franco
790d9a481baSLucio Franco## Appendix
791d9a481baSLucio Franco
792d9a481baSLucio Franco<a name="tonic-build"></a>
793d9a481baSLucio Franco### tonic_build configuration
794d9a481baSLucio Franco
795d9a481baSLucio FrancoTonic's default code generation configuration is convenient for self contained examples and small
796d9a481baSLucio Francoprojects. However, there are some cases when we need a slightly different workflow. For example:
797d9a481baSLucio Franco
798d9a481baSLucio Franco- When building rust clients and servers in different crates.
799d9a481baSLucio Franco- When building a rust client or server (or both) as part of a larger, multi-language project.
800d9a481baSLucio Franco- When we want editor support for the generate code and our editor does not index the generated
801d9a481baSLucio Francofiles in the default location.
802d9a481baSLucio Franco
803d9a481baSLucio FrancoMore generally, whenever we want to keep our `.proto` definitions in a central place and generate
804d9a481baSLucio Francocode for different crates or different languages, the default configuration is not enough.
805d9a481baSLucio Franco
806d9a481baSLucio FrancoLuckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two
807d9a481baSLucio Francopossibilities:
808d9a481baSLucio Franco
809d9a481baSLucio Franco1)  We can keep our `.proto` definitions in a separate crate and generate our code on demand, as
810d9a481baSLucio Francoopposed to at build time, placing the resulting modules wherever we need them.
811d9a481baSLucio Franco
812d9a481baSLucio Franco`main.rs`
813d9a481baSLucio Franco
814d9a481baSLucio Franco```rust
815d9a481baSLucio Francofn main() {
8162e082f8bSLucio Franco    tonic_build::configure()
817d9a481baSLucio Franco        .build_client(false)
818d9a481baSLucio Franco        .out_dir("another_crate/src/pb")
8195965daa8Stottoto        .compile_protos(&["path/my_proto.proto"], &["path"])
820d9a481baSLucio Franco        .expect("failed to compile protos");
821d9a481baSLucio Franco}
822d9a481baSLucio Franco```
823d9a481baSLucio Franco
824f82488c4SOn `cargo run`, this will generate code for the server only, and place the resulting file in
825d9a481baSLucio Franco`another_crate/src/pb`.
826d9a481baSLucio Franco
827d9a481baSLucio Franco2) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that
828d9a481baSLucio Francocrate as a direct dependency wherever we need it.
829